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