Am Di., 25. Jan. 2022 um 00:08 Uhr schrieb Wolfgang Corcoran-Mathe <xxxxxx@sigwinch.xyz>:
Hi Marc, thanks for all of this work.  I'll try to reply more quickly
in the future.

No problem at all. We all do this in our spare time.
 
On 2022-01-19 08:46 +0100, Marc Nieper-Wißkirchen wrote:
> So... a more regular, more expressive, and not hard-to-use version of
> lambda* would look like
>
> (lambda** (<var> ...) (<var> ... . <var*>)
>   <body>)
>
> If this form is applied to no more than as many arguments as there are
> variables between the first set of parentheses, a procedure is
> returned. If there are more actual arguments, any excess arguments are
> distributed over the second set of formals (and it is an error if
> there are not enough excess arguments) and then the body is evaluated.

I appreciate that this seems to fix the ambiguity of lambda*
while also (I believe) letting you do everything the current form
does.  It provides a lot of flexibility, while still being pretty
direct to implement with syntax-rules.  I doubt an implementation
of lambda** would be much longer or more complicated than that of
lambda*.

However, I think the form is syntactically much heavier, and
complicated syntax is the nemesis of currying macros.  It's easy to
imagine many programmers ignoring lambda** simply because of the
"weird" double formals.[*]  I don't mean to put ergonomics above
correctness, but I believe that a clunkier syntax should seem
warranted.  That's not how it seems to me, as I'm not yet convinced
that this new form fixes any significant problems with lambda*.

I don't find the lambda** form particularly beautiful either.  "Syntactically *much* heavier" seems to me to be a gross exaggeration, though.  It is also not quite clear to me what you refer to when you write "nemesis of currying macros".  We don't have any currying macro standardized in Scheme yet, do we?  Strictly speaking, lambda* and relatives are neither currying operators, but just return a curried procedure.  A currying operator C would take a procedure defined on n arguments and return a value that can be applied consecutively n times.  As Scheme procedures do not have a well-defined arity, we cannot define such an operator in Scheme.  That lambda* is possible is because the syntactic form lambda* knows how many arguments are expected.  We roughly have (lambda* (<x> ...) <body>) = (C (lambda (<x> ...) <body>)).  Note that the right-hand side makes sense even in the thunk case (the thunk would be evaluated by C) and that it suggests adding the thunk case back to the specification.

If we want to turn C into a true Scheme operator (that is a higher-order procedure), we have to amend it by giving it a second argument, namely n.

We would then have the following recursive definition:

(C 0 f) => f
(((C n f) x) y ...) => ((C (- n 1) f) x y ...)

This operator, which deserves the name currying operator, underlies the semantic of lambda**:

(lambda** (<x> ...) (<y> ... . <y*>) <body>)

is nothing but (C n (lambda (<x> ... <y> ... . <y*>) <body>)), where n is the number of <x>s.

The less general

(lambda* (<x> ... . <y*>) <body>)

is given by (C (n - 1) (lambda <x> ... . <y*>) <body>)

where n is again the number of <x>s.  Note that this interpretation of lambda* in terms of an actually definable currying operator suggests that the thunk case of lambda* should be forbidden.  But then it also suggests that the definition of lambda* is offset by 1.

A currying operator like C is probably more useful than the lambda* (or lambda**) form because it allows currying on the fly, while we would otherwise rely on our procedures already coming in a curried form, which they, in general, don't. (This wouldn't be a serious problem in a language with static types and ad-hoc overloading; but in Scheme, each procedure name has to either be bound to a curried form or not.)

The fold example would be then become something like (modulo the naming of "C"):
(let ((sum ((C fold) + 0))
      (product ((C fold) * 1))
      (lis '(1 2 3 4 5)))
  (values (sum lis) (product lis)))

(Of course, syntactic sugar could simplify ((C fold) ...) to something like (C* fold ...).

I'd like to get some more input as to what other reviewers think of
lambda* vs. lambda**.

[*] I recall a disscussion about the receive and let-values macros
in which one distinguished Schemer said he'd never used let-values
"because three parens is too many".

Such an argument should be considered, at least if we expect regular uses of lambda* (or lambda**).  But the latter is not yet clear to me.  Which procedures would one want to define through lambda*?

> There is another possible syntax for lambda** besides the one I gave
> that has two formal argument lists and which may be more convenient:
>
> (lambda*** (a b c | d e . f) <body>)
>
> would be (lambda** (a b c) (d e . f) <body>), where "|" is some
> keyword-like datum.

This is a bit more convenient, I think, and I'd prefer this form
over the double-formals version.  I also much prefer the version
that doesn't extend lambda, which I think would require standardized
keywords(?).

I also prefer this form a lot over the other one.

Keywords are not necessarily needed as I described in my earlier post.

In fact, the fact that lambda*** can be merged with lambda is a big point in favor of that form, for otherwise, we would have a proliferation of mutually incompatible extensions to the basic lambda syntax. (We also have proposals for optional and for keyword arguments, ...).
 
But, again, I'm not yet sold on lambda** or its variants.  Hopefully
we can get some more opinions.

Can you explain why you would prefer lambda* over lambda***?

lambda*** is more expressive and doesn't have obscure corner cases while syntactically as light-weight.
Marc