On Mon, May 19, 2025 at 8:57 AM Eric Blake <ebl...@redhat.com> wrote:
>
> 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($@)))')')

Probably leftover from other experiments. I meant to use apply as
apply(`myfunction', `arg1', ..., `argn'), and it should be equal to
myfunction(`arg1', ..., `argn').

> >
> > 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)')')

That's true! I wrote this code before attempting the m4
implementation, so I knew much less about m4.

> >
> > 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)')

No good answer here, I was just having fun.

> >
> > 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'')')')

This should probably be fine, I most likely overshot it during a
monkey-typewriter exercise.

> >
> > # 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

Here you can see a difference between our curries: My curry takes an
arbitrary number of arguments and curries the rest, e.g. partial
function application.

> 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.

Maybe I misunderstood what it means to write a truly generic lambda,
but what about this usage of my functions above?

define(f, `lambda(`eval($1 + $2)')')
f`'(3,4) is apply(`f', 3, 4)

Regards,
Nikolaos Chatzikonstantinou

Reply via email to