On Sun, May 18, 2025 at 08:05:21AM -0400, Nikolaos Chatzikonstantinou wrote:
> > And speaking of Turing complete, Doug McIlroy has an interesting demo
> > at how not only is m4 Turing complete with all of its builtins, but if
> > you strip it down to JUST the define macro, it is still Turing
> > complete:
> >
> > https://www.tuhs.org/mailman3/hyperkitty/list/t...@tuhs.org/thread/7ZRYUMZ2QNKE24QWUFT7MRK74COT7BIQ/
> 
> I only briefly skimmed over it, it has some cool techniques. They
> definitely went the academic route to prove this by creating a model
> for arithmetic. I prefer the hand-wavy route: m4 is turing complete
> merely by supporting syscmd(). I want to share my own m4 code that I
> wrote when I studied m4 to fix issues with Guile's Autotools macros
> (haven't gotten around to that yet) Once I managed to implement lambda
> functions in a language that didn't originally support them I was
> pretty convinced of its coolness. I also managed to cram in currying,
> in a way completing a challenge mentioned by Kenneth J. Turner in
> "Exploiting the m4 Macro Language":
> 
> > Unfortunately a single Curry macro cannot be defined since the number of 
> > parameters to be fixed must be known.
> 
> I amused the author by e-mailing him my solution. M4 has enough
> syntactic manipulation power that you can implement a lot of
> functional programming in it, like foldr and so on. I actually think
> it's a good tool to teach syntactic manipulation to talented students,
> *especially* if we can get an IDE (debugger?) around it, that shows
> all the steps of expansion. Of course debugmode() can do this, but
> it's a bit more technical than I'd like it to be.
> 
> --- m4 source, cut here ---
> 
> define(apply,`ifelse($1,`',`',$1(shift($@)))')

I don't see "apply" used in the rest of your example.  Why is it here,
and does it even do what you thing it should?

If you meant for apply(`foo', 1, 2, 3) to expand to foo(1) foo(2)
foo(3), then it should probably be:

define(`apply', `ifelse(`$2',,,
  `$1(`$2')`'apply(`$1', shift(shift($@)))')')

> 
> define(id, $@)
> define(
>   peel,
>   `ifelse(eval($1 == 0),1,`id(``$2'')',eval($1 ==
> 1),1,`id(`$2')',`peel(decr($1),$2)')')

Slightly more efficient to drop the evals, and declare this as:

define(`peel', `ifelse($1, 0, `id(``$2'')',
  $1, 1, `id(`$2')', `peel(decr($1),$2)')')

> 
> define(
>   randhex,
>   `esyscmd(`od -N$1 -An -tx1 /dev/urandom | tr -d " \n"')')

This depends on GNU m4.  Why is an incrementing counter not good
enough that you need to risk arbitrary collisions with a pseudo-random
string instead?  And if you really need random macro characters with
just POSIX m4 (rather than depending on the GNU extension of esyscmd),
you can (ab)use mkstemp() for that purpose, along the lines of:

# randalnum() - produce 6 bytes of psuedo-random alphanumerics
define(`randalnum', `_randalnum(mkstemp(`m4XXXXXX'))')
define(`_randalnum', `syscmd(`rm -f $1')substr(`$1', 2)')


> 
> define(
>   lambda__aux,
>   `define($1,$2)`$1'')
> 
> define(
>   lambda,
>   `peel(3,``lambda__aux('lambda_``''`randhex(5), ``$1'')'')')

Couldn't this also be written with just peel(2) and one less layer of
inner quoting:

define(`lambda', `peel(2, `lambda__aux('lambda_``''`randhex(5), ``$1'')')')

> 
> # curry(f,arg1,...,argn)
> # Partially applies arg1, ..., argn to f.
> # Note that there must be at least one argument, i.e. n >= 1.
> define(
>   curry,
>   `lambda(`$1(shift($@),'$`'@`)')')

Here's how the m4 manual declared a version of curry:

divert(`-1')
# curry(macro, args)
# Expand to a macro call that takes one argument, then invoke
# macro(args, extra).
define(`curry', `$1(shift($@,)_$0')
define(`_curry', ``$1')')
divert`'dnl

Writing curry() in m4 is relatively easy.  Writing a truly generic
lambda() that can define an anonymous m4 macro is much tougher,
because of the inability to easily generate '$1' and friends into the
macro body without having it be prematurely expanded.  In the
literature, I have seen reference to the "M5" language, where writing
lambda functions was easier because that language specifically treats
"$$1" in a macro body as expanding to literal "$1" (so writing macros
to write other macros gets a whole lot easier), unlike m4 where it is
a literal "$" followed by the immediate expansion of "$1".  One of my
vague ideas on branch-2.0 was to implement the ability for GNU m4 to
implement the M5 language (although I'd have to actually research if
there are any publicly accessible implementations of M5 to compare
against, more than just the vague reference in that document I read).

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.
Virtualization:  qemu.org | libguestfs.org


Reply via email to