What is the canonical way to write a for-loop macro?

At a basic level TeX does not provide a for-loop construct, but allows tail-end recursion, however Knuth defined \loop in the plain macros. Coders have been coming with variations of their own ever since.

The ifthen package provides the whiledo loop and there is a forloop macro. TikZ provides similarly a foreach macro.

Is there a canonical way to write a forloop in LaTeX, TeX and friends. How would you write the following?

\def\triangle#1{{\def\bull{}%
\count1=0
\loop
\edef\bull{$\bullet$\bull}
\ifnum\count1<#1
\centerline{\bull}
\vskip-7.7pt
\repeat
\vskip 7.7pt\relax}}


which will produce a triangle made of dots

        *
*    *
*     *    *


As typically found in programming texts, can you re-write it in a better way?

-
Should this be community wiki? There is no definite answer. And there are already three different “right” answers. – Caramdir Sep 30 '10 at 19:37
@caramdir I agree it should be a community wiki, I will change after 24 hours. – Yiannis Lazarides Sep 30 '10 at 19:48
Changed to community wiki. – Yiannis Lazarides Sep 30 '10 at 20:46
I'll CW the answers, then – Joseph Wright Oct 1 '10 at 15:45

Here is how I will do it in ConTeXt:

\def\TRIANGLE#1%
{\dorecurse{#1}
{\centerline{$\dorecurse{\recurselevel}{{\bullet}}$}}}

\starttext \TRIANGLE{4} \stoptext


The extra braces around \bullet is so that is has math class ord rather than bin.

-
what does \recurselevel do? – Yiannis Lazarides Sep 30 '10 at 16:59
\recurselevel is the value of the current "level". So, in the first iteration it is 1, in the second iteration it is 2, and so on. Similar to the value of \count1 in your example. – Aditya Sep 30 '10 at 17:01

A LuaTeX solution could look like this:

\def\luatriangle#1{%
\directlua{

for i = 1,#1 do
tex.sprint(string.format("\\centerline{\%s}\\vskip-7.7pt", string.rep("$\\bullet$",i) ))
end

}}


There is only one little problem here, because the macro contains \\ and TeX complains about it. So the solution (and the advice) is to put all Lua code into a separate file and do something like:

\def\luatriangle#1{%
\directlua{
dofile("loop.lua")
loop(#1)
}}


and the Lua file (loop.lua):

function loop( count )
for i = 1,count do
tex.sprint(string.format("\\centerline{%s}\\vskip-7.7pt", string.rep("$\\bullet$",i) ))
end
end


Not the best LuaTeX example. The ugly part is the string.format(...) line, but I think the loop part is very readable.

-

A couple of LaTeX3 solutions. You could do this as a replication

\documentclass{article}
\usepackage{expl3,fixltx2e}
\begin{document}
\ExplSyntaxOn
\int_new:N \l_my_int
\prg_replicate:nn { 3 } {
\int_incr:N \l_my_int
\centerline { \prg_replicate:nn { \l_my_int } { $$\bullet$$ } }
}
\ExplSyntaxOff
\end{document}


Alternatively, the while-do approach:

\documentclass{article}
\usepackage{expl3,fixltx2e}
\begin{document}
\ExplSyntaxOn
\int_new:N \l_my_int
\intexpr_while_do:nn { \l_my_int < 4 } {
\int_incr:N \l_my_int
\centerline { \prg_replicate:nn { \l_my_int } { $$\bullet$$ } }
}
\ExplSyntaxOff
\end{document}


I should add that there are limitations on the replication system in expl3 (usually at the tens of thousands of replications!). So for a really big loop while-do might be safer.

-

Here's a Plain TeX macro that's similar to the one in the question except it handles things like being used from horizontal mode or a change to \mathsurround.

\def\triangle#1{%
\par
\vbox{%
\baselineskip4.3pt
\m@th
\toks@{}%
\count@#1
\loop\ifnum\count@>\z@
\toks@\expandafter{\the\toks@{\bullet}}%
\centerline{$\the\toks@$}%
\repeat
}%
}


It should be pretty fast too. \setbox0\vbox{\triangle{500}} takes about .2 seconds on my laptop (an average over 1000 trials). Using 100 (about the largest that fits on a page) instead of 500 takes .007 seconds.

I also find the loop pretty readable, modulo the @ in the names which just takes getting used to and isn't part of the loop really.

-

Kees van der Laan wrote an article titled "Paradigms: Loops" which discusses several different TeX-based approaches for writing loops. See MAPS 17.

-

Another solution, this time using Metapost. Again, this is tuned for ConTeXt, but should be easy to modify for LaTeX.

\startMPinclusions[+]
vardef TRIANGLE(expr n) =
bullet_sep  := 5bp ;
bullet_size := 3bp ;

path bullet ;
bullet := fullcircle scaled bullet_size ;

for i := 1 upto n:
y := -i * bullet_sep ;
x := - (i-1)/2 * bullet_sep ;
for j := 1 upto i :
fill bullet shifted ( x + j*bullet_sep, y) ;
endfor ;
endfor ;
enddef ;
\stopMPinclusions

\def\TRIANGLE#1{\startMPcode TRIANGLE(#1); \stopMPcode}

-
Hmm, not entirely inappropriate for the task given, but I got the impression it was just an example task and he really wanted to know about TeX-level looping... – SamB Dec 1 '10 at 5:16
... of course, for all I know, that is TeX level :-) – SamB Dec 1 '10 at 5:17

This is essentially same as Partick's solution, but adapted to ConTeXt, and much more readable, IMO

\def\TRIANGLE#1{\ctxlua{third.triangle(#1)}}

\startluacode
third = third or {}
function third.triangle(n)
for i=1,n do
context.centerline(string.rep("$\\bullet$", i))
context.blank{ "-7.7pt" }
end
end
\stopluacode


See Context lua documents manual for details of how context function works.

-