Here's my humble attempt to answering this question. Since David asked me in the chatroom whether my answer would include ducks, here's one for obvious reasons: :)
Prologue
I personally believe that complexity is in the eye of the beholder. I commented this somewhere in this site, but IMHO it's worth mentioning again: at the end of the day, complicated constructs are a matter of taste and ideology; if you grew up accustomed to a certain construct or command pattern, there's a tendency for labeling different representations of the same logic as wacky or strange or excessively verbose. And, once again, I have the impression Maslow's hammer applies here as well: "I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail."
A macro is a rule or pattern which specifies how a sequence of symbols is replaced by other sequence of symbols. When you transform the occurrence of a macro into the resulting sequence, you have what we call macro expansion.
Let's see a macro in action (C code):
#define AGE 25
...
printf("The minimum age is %d.", AGE);
It is important to observe that AGE
is not a pointer; during preprocessing, every and each occurrence of it in the source code is literally replaced by its content - it's a lexical replacement.
In general, what we have here is a lexical macro, which might occur and expand during the lexical analysis. We can have a syntactic macro as well, but that's another story. :)
A macro can be simple or parametric. The latter, as the name indicates, takes arguments in order to fill in the formal parameters according to the macro definition. I won't talk about syntax here because it adds no value at the moment. :)
So, as we can guess, a macro processor (or expander) finds macros in a sequence of symbols and expands them accordingly. Simple as that. Symbols that are not macros are simply reproduced verbatim. In other words, a macro expander only "sees" macros; everything else is "discarded". A very rough and naive workflow could be represented as the following FSM:
I'd add that this FSM is recursively called when you expand x
through process(x)
. See the beauty in it? You always end up in searching for symbols, which only need to be reproduced verbatim, or macros, which are expanded. I think it's better with a concrete example. Let's try the following code:
\def\hello#1{Hello #1}
\def\myname{Paulo}
\hello{\myname}
Let's ignore the macro definitions for a minute and focus only on the third line: we found a parametric macro which is \hello
, so let's expand it! We will end up with Hello \myname
, so let's try our FSM on this sequence as well: Hello
is processed as is, but \myname
is yet another macro! Expanding it will give Paulo
, so let's try our FSM on this sequence: Paulo
is not a macro, so we simply return it as is; now back to our last last call, we have Hello Paulo
which has no symbols left for analysis, so let's go back to \hello
, which is correctly expanded to Hello Paulo
.
In order to define macros, we need to use something. So let's introduce the concept of primitives, which are special language commands designed to help build new constructs on top of them. A primitive can define or redefine a macro, perform an integer operation, test conditional expressions, and so on. It depends on each language, so our mileage might vary.
Back to TeX. TeX is a macro expansion language, so it basically searches for macros and expands them when found. That's it. IMHO the concept is so simple yet so beautiful. It is like playing Pacman – there's nothing complicated about it: eat pills, avoid ghosts. :)
Did I mention Pacman?
Now, let's talk about LaTeX. Of course, there's much more to it, but what we have now is a whole bunch of new macros built from TeX primitives. These new elements aim at easing the TeX usage by supplying new ways of writing documents. For example, instead of writing
\par \begingroup \renewcommand \thefootnote {\@fnsymbol \c@footnote }
\def \@makefnmark {\rlap {\@textsuperscript {\normalfont \@thefnmark }}}
\long \def \@makefntext ##1{\parindent 1em\noindent \hb@xt@ 1.8em{\hss
\@textsuperscript {\normalfont \@thefnmark }}##1}\if@twocolumn \ifnum
\col@number =\@ne \@maketitle \else \twocolumn [\@maketitle ]\fi \else
\newpage \global \@topnum \z@ \@maketitle \fi \thispagestyle{plain}
\@thanks \endgroup \setcounter {footnote}{0}\global \let \thanks \relax
\global \let \maketitle \relax \global \let \@maketitle \relax \global
\let \@thanks \@empty \global \let \@author \@empty \global \let \@date
\@empty \global \let \@title \@empty \global \let \title \relax \global
\let \author \relax \global \let \date \relax \global \let \and \relax
you can now go with \maketitle
. And things work.
Just a silly example on how things can be constructed on top of simple concepts: I wrote a very simple macro expander which could calculate the factorial of a number based on integer addition and multiplication. But my language doesn't have integer addition and multiplication, only integer increment and decrement operations. What did I do? :)
Behold the magic of composition of primitive recursive functions!
Sum
- Base case: sum (0, y) = y
- Inductive step: sum (S(x), y) = S(sum (x, y))
Multiplication
- Base case: mult (0, y) = 0
- Inductive step: mult (S(x), y) = sum (mult (x, y), y)
Factorial
- Base case: fact (1) = 1
- Inductive step: fact (S(n)) = mult (S(n), fact (n))
If you guys think TeX syntax is complicated, I have good news for you: here is the code of my macro language which implements the operations above (my macro expander is available here, if someone wants to take a look at it):
Sum
\! define (\@ \wsum(\#x#\,\#y#\)w\ =
\w\q check condition (\$\% is zero (\&\*x*\&\) %\$\,
\$\;y;\$\, \$\% increment (\*\| sum (
\&\g decrement (\j\ixi\j\) g\&\, \&\iyi\&\
) |\*\) %\$\) q\w\@\) !\
Multiplication
\! define (\@ \wmult(\#x#\,\#y#\)w\ =
\w\q check condition (\$\% is zero (\&\*x*\&\) %\$\, \$0$\,
\$\% sum (\*\| mult (\&\h decrement (\u\vxv\u\) h\&\,
\&\iyi\&\) |\*\, \*\iyi\*\) %\$\) q\w\@\) !\
Factorial
\! define (\@ \wfact(\#val#\)w\ =
\w\q check condition (\$\% is greater than (\&\*val*\&\,
\&1&\) %\$\, \$\< mult (\X\YvalY\X\,
\X\;fact(\-\y decrement(\+\,val,\+\) y\-\)
;\X\) <\$\, \$1$\) q\w\@\) !\
My point here is: things are not complicated, they are the way they were designed. :)
Take LaTeX3, for instance: they offer, amongst several things, CSV support out of the box, provided by high level commands built on top of TeX/LaTeX. Or get Heiko's hyperref
where there's direct PDF manipulation. All built from the very simple yet powerful concepts discussed earlier.
IMHO LaTeX is not complicated. The question is, if you need something different for your code, you need either to use an existing package (someone wrote the code before) or provide the solution by yourself, like I did with factorial and my macro language. When the infrastructure does not offer what we need at the moment, we are completely free to implement it by ourselves. It's surely possible. :)
Mere expansions, my dears. Mere expansions. :)
\makeatletter
and\makeatother
are ridiculous and so on. Probably also Is LaTeX outdated should be closed for the same reason, but its spirit is quite different. My opinion is that programming in TeX requires a different approach from other languages, but it's not harder or easier.