Here's some code I'm developing for leisure. Save the following code as extforeach-code.tex
% extforeach-code.tex
\ExplSyntaxOn
\providecommand\fpeval{\fp_eval:n}
\NewDocumentCommand{\nforeach}{ m +m }
{
\tl_clear:N \l__manual_nforeach_type_tl
\keys_set:nn { manual/nforeach }
{
type=integers,start = 1, step = 1, end = 0,
}
\keys_set:nn { manual/nforeach } { #1 }
\__manual_nforeach_exec:n { #2 }
}
\int_new:N \g__manual_foreach_map_int
\int_new:N \g__manual_fp_map_int
\tl_new:N \l__manual_nforeach_type_tl
\keys_define:nn { manual/nforeach }
{
type .choice:,
type .value_required:n = true,
type/integers .code:n = \tl_set:Nn \l__manual_nforeach_type_tl { integers },
type/fp .code:n = \tl_set:Nn \l__manual_nforeach_type_tl { fp },
type/alph .code:n = \tl_set:Nn \l__manual_nforeach_type_tl { alph },
type/Alph .code:n = \tl_set:Nn \l__manual_nforeach_type_tl { Alph },
start .tl_set:N = \l__manual_nforeach_start_tl,
step .tl_set:N = \l__manual_nforeach_step_tl,
end .tl_set:N = \l__manual_nforeach_end_tl,
}
\cs_new_protected:Nn \__manual_nforeach_exec:n
{
\int_gincr:N \g__manual_foreach_map_int
\str_case:Vn \l__manual_nforeach_type_tl
{
{integers}{\__manual_nforeach_exec_integers:n { #1 }}
{fp} {\__manual_nforeach_exec_fp:n { #1 }}
{alph} {\__manual_nforeach_exec_alph:Nn \int_to_alph:n { #1 }}
{Alph} {\__manual_nforeach_exec_alph:Nn \int_to_Alph:n { #1 }}
}
\int_gdecr:N \g__manual_foreach_map_int
}
\cs_generate_variant:Nn \str_case:nn { V }
\cs_new_protected:Nn \__manual_nforeach_exec_integers:n
{
\int_step_inline:nnnn
{ \l__manual_nforeach_start_tl }
{ \l__manual_nforeach_step_tl }
{ \l__manual_nforeach_end_tl }
{ #1 }
}
\cs_new_protected:Nn \__manual_nforeach_exec_alph:Nn
{
\cs_set:cn { __manual_nforeach_alph_ \int_use:N \g__manual_foreach_map_int :n } { #2 }
\cs_generate_variant:cn
{ __manual_nforeach_alph_ \int_use:N \g__manual_foreach_map_int :n }
{ f }
\int_step_inline:nnnn
{ \int_from_alph:f { \l__manual_nforeach_start_tl } }
{ \l__manual_nforeach_step_tl }
{ \int_from_alph:f { \l__manual_nforeach_end_tl } }
{
\use:c { __manual_nforeach_alph_ \int_use:N \g__manual_foreach_map_int :f }
{ #1 { ##1 } }
}
}
\cs_generate_variant:Nn \cs_generate_variant:Nn { c }
\cs_generate_variant:Nn \int_from_alph:n { f }
\cs_new_protected:Nn \__manual_nforeach_exec_fp:n
{
\fp_step_inline:nnnn
{ \l__manual_nforeach_start_tl }
{ \l__manual_nforeach_step_tl }
{ \l__manual_nforeach_end_tl }
{ #1 }
}
\NewDocumentCommand{\lforeach}{ s O{} m +m }
{
\IfBooleanTF{#1}
{
\manual_lforeach:non { #2 } { #3 } { #4 }
}
{
\manual_lforeach:nnn { #2 } { #3 } { #4 }
}
}
\cs_new_protected:Nn \manual_lforeach:nnn
{
\keys_set:nn { manual/lforeach } { single }
\keys_set:nn { manual/lforeach } { #1 }
\clist_set:Nn \l__manual_lforeach_list_clist { #2 }
\int_gincr:N \g__manual_foreach_map_int
\__manual_lforeach_define:n { #3 }
\clist_map_inline:Nn \l__manual_lforeach_list_clist
{
\use:c { __manual_lforeach_ \int_use:N \g__manual_foreach_map_int _action:w } ##1 \q_stop
}
\int_gdecr:N \g__manual_foreach_map_int
}
\cs_generate_variant:Nn \manual_lforeach:nnn { no }
\cs_new_protected:Nn \__manual_lforeach_define:n
{
\exp_last_unbraced:NcV
\cs_set:Npn
{ __manual_lforeach_ \int_use:N \g__manual_foreach_map_int _action:w }
\l__manual_lforeach_format_tl
\q_stop
{#1}
}
\keys_define:nn { manual/lforeach }
{
format .tl_set:N = \l__manual_lforeach_format_tl,
single .code:n = \tl_set:Nn \l__manual_lforeach_format_tl { ##1 },
double .code:n = \tl_set:Nn \l__manual_lforeach_format_tl { ##1/##2 },
triple .code:n = \tl_set:Nn \l__manual_lforeach_format_tl { ##1/##2/##3 },
}
\ExplSyntaxOff
Now your document can be
\documentclass{article}
\usepackage{xparse,xfp}
\input{extforeach-code.tex}
\newcommand\linkLength{1.2}
\newcommand\jointRadio{0.2}
\newcommand\jointLength{0.6}
\newcounter{lines}
\begin{document}
\lforeach[format=#1 #2 #3]{
0 0 0, % N°0
0 0 0, % N°1
0 0 \linkLength, % N°2
0 0 \jointLength, % N°3
0 {\jointLength*0.5} {\linkLength + \jointRadio}, % N°4
}
{%
\stepcounter{lines}Line \thelines\ is \fpeval{#1}~\fpeval{#2}~\fpeval{#3}\par
}
\end{document}
With the help of xfp we can even evaluate expressions.
The format key sets up a template for each item in the comma separated list given as first mandatory argument, here #1 #2 #3 means the item will consist of things like
<subitem><space><subitem><space><subitem>
The second mandatory argument is code that uses the arguments set up in the template. There are abbreviations single (default if nothing is specified), double and triple that stand for
format=#1
format=#1/#2
format=#1/#2/#3
respectively.

\for-macro where you can specify the level of expansion needed with the list-holding argument. – Ulrich Diez Mar 01 '19 at 02:54