On Mon, May 19, 2025 at 09:10:45AM -0400, Nikolaos Chatzikonstantinou wrote:
> > >
> > > # 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.
>

The m4 manual's curry is indeed weak; but can easily be fixed by
changing that $1 to $@ (and improving the doc comments):

$ m4
define(`curry', `$1(shift($@,)_$0')dnl
define(`_curry', `$@)')dnl
define(`hairy', `.$2-$1.$4-$3')dnl
curry(`hairy', a)(b, c, d)
.b-a.d-c
curry(`hairy', a, b)(c, d)
.b-a.d-c
curry(`hairy')(a, b, c, d)
.b-a.d-c
curry(`hairy', a, b, c, d)()
.b-a.d-c

Thanks for making me notice this!

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

I may not be expressing myself well, but my understanding of languages
with anonymous functions is that any higher-order function that can
take a function name as an argument:

map(FUNC, 1, 2) => produces the list consisting of FUNC(1) and FUNC(2)

should also be able to take an application of the lambda function (aka
produce an anonymous function) in place of a previously named
function.  That means inline code:

map(lambda(`eval($1+1)'), 1, 2) => produce the list 2, 3

but lambda should be usable anywhere a function name can be, and in m4
that is not trivial:

map(lambda(`eval($1+1)'), 1, 2)

is different than:

define(`listplusone', `map(lambda(`eval(XXX+1)'), $@)')
listplusone(1, 2) => I want this to produce the list 2, 3

because in the latter, if I write $1 for XXX, that gets evaluated
prematurely during the expansion of listplusone, when I wanted it to
be expanded late in the context of the anonymous function that is
taking arguments from map.  Since m4 does NOT recognize $$1 as
expanding to '$1' in the helper function (the way M5 would), I can't
replace the XXX with $$1 to get that effect either; I would have to
resort to tricks like dol(1) and additional arguments that rewrite the
string passed to lambda into something that actually produces a
correct definition of listplusone.

So you argue that I should instead write:

define(`listplusone', `map('lambda(`eval(XXX+1)')`, $@)')

but that only delays the problem, because I can dream up writing a
macro that in turn wants to define a macro that uses lambda.

The ultimate example would be writing the Y combinator (a lambda
function that implements the lambda function), or the related Z
combinator.  I have tried in the past to accomplish that in m4 syntax,
and failed in my attempts so far (although I haven't tried recently).
So I would welcome anyone that can demonstrate a working Y combinator
in m4 as a way to implement anonymous recursion.

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


Reply via email to