1

So I was looking for a way to do something in latex, and I ran across the following MWE:

\documentclass{article}

\def\foo{1} \def\bar#1 #2{% \expandafter\def\expandafter\foo\expandafter{\foo {}#1} \let\next\bar% \ifx#2\relax% \let\next\relax% \fi% \next#2% } \bar 2 3 4 \relax

\begin{document} \foo \end{document}

Basically what it does is it starts with foo equal to 1 and it concatenates the numbers 2, 3, 4 afterwards and you end up with it outputting 1234.

I understand how most of this is working, but I'm confused on two different fronts (which are probably the same front?) and so I was wondering if someone could explain what's happening. At the end I'll mention what I'm trying to do, to hopefully show motivation as to why I'm asking.

Basically, my first confusion has to do with: \def\bar#1 #2. Normally I thought you had to add an optional parameter like \def\bar[2]{#1 #2} or something, but it seems like this is doing something differently? Is #1 #2 basically saying 2 is set as #1 then there's a space and then 3 4 is set as #2?

My second confusion is with the term \next. This is also in conjunction with how this is looping because it seems like it shouldn't? I'm assuming it's the \next operator that's doing it, so I was wondering how this works.

My motivating question comes from me wanting to do something like this, but to stop after some number. So for example, I want to be able to call: \bar{4} 2 3 4 5 6 7\relax and have the output be 1234 (so it stops when it hits 4). Since I'm not understanding how the looping is happening and how \def is working, I'm not sure how to go about this.

Thanks in advance.

  • 1
    You have one unwanted space in your macro at the end of the line 5, but next four lines have percent character irrelevantly. Maybe, you want to start to read something about how TeX works, for example http://petr.olsak.net/ftp/olsak/optex/tex-nutshell.pdf – wipet May 04 '21 at 00:33

2 Answers2

2

\next is not a predefined operator. It's just a scratch control sequence that gets redefined several times during the loop.

What happens when \bar is called (by the way it's a bad name, because \bar is a predefined control sequence that's used for a math accent)?

Its definition has parameter text #1 #2, which means that TeX will absorb everything until the first space token, assigning it to the parameter #1 and then a further token or braced list of tokens, assigning it to the parameter #2.

Next a long string of \expandafter tokens is executed. Let's say you have the input \bar 2 3 4 \relax.

In this case #1 is 2 and #2 is 3. Then TeX will do

\expandafter\def\expandafter\foo\expandafter{\foo {}2}

Each \expandafter tokens is touched in turns and the last one causes \foo to be expanded to its current meaning, so you get

\def\foo{1{}2}

Such definition is stored and then the next line is looked at, so \next is assigned the same meaning as \bar. Then #2 is compared to \relax; in this case the test returns false, because #2 is 3, so the text up to \fi is skipped over. It remains

\next#2

so now TeX is confronted with

\bar3 4 \relax

The same thing now happens twice, so we arrive at

\def\foo{1{}2{}3{}4}

but now there's a difference, because #2 now is \relax. So the \ifx test returns true and TeX does \let\next\relax, so the last instruction becomes

\relax\relax

and the loop ends.

Not a particularly good way to do the task, but it works. By the way, a % is missing at the end of the \expandafter line and there are redundant ones in the next two lines.

If you want to stop the loop earlier you need to modify the macros and introduce a counter to index the number of runs.

Maybe a more modern loop is what you want.

\documentclass{article}

\ExplSyntaxOn

\seq_new:N \l__aram_addto_seq

\NewDocumentCommand{\aramaddto}{mom} { % first split the final argument at spaces (here ~ stands for a space) \seq_set_split:Nnn \l__aram_addto_seq { ~ } { #3 } \IfNoValueTF { #2 } {% no optional argument, add everything \tl_set:Nx #1 { \exp_not:V #1 \seq_use:Nn \l__aram_addto_seq { } } } {% optional argument, only add as many items as required \seq_map_indexed_inline:Nn \l__aram_addto_seq {% ##1 is the current index, ##2 is the item \int_compare:nTF { ##1 < #2 } {% still wanting to add \tl_put_right:Nn #1 { ##2 } } {% flush \seq_map_break: } } } }

\ExplSyntaxOff

\begin{document}

\newcommand{\fooA}{1}

\aramaddto\fooA{2 3 4}

Should print 1234: \fooA

\newcommand{\fooB}{1}

\aramaddto\fooB[4]{2 3 4 5 6}

Should print 1234: \fooB

\end{document}

enter image description here


After the comments, here's a way how to accomplish the aim, that is, input a list of numbers and multiplying them all, with the option to stop when one of the numbers is greater than a stated upper bound.

\documentclass{article}
\usepackage{xfp}

\ExplSyntaxOn

\NewDocumentCommand{\genfactorial}{om} { \IfNoValueTF { #1 } { \aram_genfactorial:en { \clist_item:nn { #2 } { -1 } } { #2 } } { \aram_genfactorial:nn { #1 } { #2 } } }

\seq_new:N \l__aram_genfactorial_seq

\cs_new_protected:Nn \aram_genfactorial:nn { \seq_clear:N \l__aram_genfactorial_seq \clist_map_inline:nn { #2 } { \int_compare:nTF { ##1 <= #1 } { \seq_put_right:Nn \l__aram_genfactorial_seq { ##1 } } { \clist_map_break: } } \fp_eval:n { \seq_use:Nn \l__aram_genfactorial_seq { * } } } \cs_generate_variant:Nn \aram_genfactorial:nn { e }

\ExplSyntaxOff

\begin{document}

\genfactorial{1,2,3,4}

\genfactorial[45]{1,2,3,4,5,6,45,67,89}

\end{document}

Using a comma separated list seems better than separating items with spaces.

enter image description here

Update

With expl3 released 2021-05-07, we can make the above fully expandable, due to the new \clist_map_tokens:nn function.

The check is made with 0 as default optional argument, assuming that the input will consist of positive numbers.

The given input is mapped item by item and *<item> is appended, but the <item> is transformed into 1 if the optional argument is 0 or the upper bound is exceeded.

\documentclass{article}
\usepackage{xfp}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\genfactorial}{O{0}m} { \aram_genfactorial:nn { #1 } { #2 } }

\cs_new:Nn \aram_genfactorial:nn { \fp_eval:n { 1 \clist_map_tokens:nn { #2 } { __aram_genfactorial:nn { #1 } } } } \cs_new:Nn __aram_genfactorial:nn { \int_compare:nTF { #1 == 0 } {% no bound * #2 } {% #1 is the upper bound \int_compare:nTF { #2 <= #1 } { * #2 } { * 1 } } }

\ExplSyntaxOff

\begin{document}

\genfactorial{1,2,3,4}

\genfactorial[4]{1,2,3,4,5,6,45,67,89}

\genfactorial[7]{1,2,3,4,5,6,45,67,89}

\genfactorial[45]{1,2,3,4,5,6,45,67,89}

\edef\test{\genfactorial{1,2,3,4,5,6,45,67,89}}\test

\end{document}

The last line shows that the function is indeed fully expandable.

enter image description here

egreg
  • 1,121,712
  • That makes more sense. (I know bar is used in math, but wanted to keep consistent with the example. And since def overwrites things anyways, it wasn't to big of a problem for MWE)

    I like your new method but it brings up so many new questions. Is there documentation for the "new way" of doing things? (I want to take these numbers, put a multiplication sign between them and then plug it into the package fp in order to create a factorial operator. )

    – Aram Papazian May 03 '21 at 22:33
  • Maybe my document is misbehaving, but the above doesn't work when I get into two digit numbers: \aramaddto\fooB[45]{2 3 4 5 6 45 67 89} is printing out all of 123456456789 instead of stopping at 45? – Aram Papazian May 03 '21 at 22:39
  • Ok, I figured it out. I think int_compare should be ##2 < #2 ? Is that correct? – Aram Papazian May 03 '21 at 22:58
  • 1
    @AramPapazian It seems I misunderstood your target: so the optional number is the upper bound after which the collection should stop, not the number of items to collect/ – egreg May 04 '21 at 07:30
  • 1
    @AramPapazian I added code that does what you seem to want. – egreg May 04 '21 at 08:09
  • Yup! I guess maybe I hadn't explained correctly. Sorry about that. This looks perfect! Thank you for the help <3 I need to learn latex3. It seems super powerful – Aram Papazian May 04 '21 at 14:01
0

The looping is more of an recursion. It eats two arguments at a time.

First pass: it sees \bar 2 3; here #1->2 and #2->3. The number 2 is concatenated to \foo, \next is set to be \bar (since #2 is not \relax).

Then it is told to process \next #2 which is the same as \bar 3. But that is missing one argument! So it looks for the next argument and in effect you process \bar 3 4. You run though the same thing again, and the next time, however, you end up processing \bar 4 relax.

This time, after plopping 4 into \foo, in the \ifx you see that #2 is \relax, so \next is told to no longer be \bar. And so the recursion stops.

Willie Wong
  • 24,733
  • 8
  • 74
  • 106