Hi John,

your proposal would mean that keyword arguments can generally only be used when the procedure name is given in operator position.  In my opinion, this is a very sensible restriction because it allows an always-zero-overhead implementation (as your syntax-case implementation shows).

Note that there should be some definition of the ":" macro even for your syntax-case implementation so that ":" is matched by binding and not by name.  E.g.:

(define-syntax :
  (lambda (stx)
    (syntax-violation ': "invalid use of auxiliary syntax" stx)))

What's a bit unattractive is the position of ":" in the calls.  If we take the symbol literally, it should come after the parameters.  Is there another sensible symbol that would convey the meaning better?  (foo 10 20 (-> c) 30)???

As far as I can see, your proposal has the advantage of supporting a lot of native keyword systems *natively*.  However, I wouldn't see it necessarily as part of a standard that also includes a sufficiently rich low-level macro system because in such a system, much simpler/nicer syntax would be implementable.  E.g. (foo 10 20 (c -> 30)) if that is what is wanted, etc.)

Cheers,

Marc


Am So., 8. Aug. 2021 um 05:26 Uhr schrieb John Cowan <xxxxxx@ccil.org>:
Here's the interface:

(define/kw (<name> <var> ... (kw-var ...) body)  [syntax]
  where kw-var is either var or (var default)

Defines a procedure that accepts required arguments and keyword arguments with or without default values (the default default is #f).   This proposal doesn't provide lambda/kw because that would preclude the syntax-case implementation.

(: <identifier>) [syntax]

Expands to an appropriate keyword notation for the implementation.   This is used in calls only,

Examples:

(define/kw (foo a b (c (d 0))) (list a b c d))

is called using

(foo 10 20) => (10 20 #f 0)
(foo 10 20 (: c) 30) => (10 20 30 0)
(foo 10 20 (: d) 40) => (10 20 #f 40)
(foo 10 20 (: c) 30 (: d) 40) => (10 20 30 40)
(foo 10 20 (: d) 40 (: c) 30) => (10 20 30 40)

There are three general implementations of this:

The syntax-case implementation (SISC, Chez, Vicare, Larceny, Ypsilon, Mosh, IronScheme, SXM, Chibi, Loko) expands define/kw into a syntax-case macro that sorts the provided keywords into alphabetical order, calls an underlying function whose lambda-list is (a b c d), and provides any missing arguments according to the defaults.  In addition, foo in operand position expands into the underlying function itself.  There is no definition of the : macro, because it only appears in invocations of foo.

The native implementations (first group: Chicken, Gambit, Bigloo; second group: Gauche, Sagittarius, STklos; S7; Guile) expands define/kw into define, and function calls have explicit keywords.   The definition of the : macro expands the name into a correctly formatted keyword.

In the first group, the keywords in define/kw are preceded by #!key and (: c) expands to c:.

In the second group, the keywords in define/kw are preceded by :key and (: c) expands to :c.

In S7, define/kw expands to define* rather than define, the keywords in define-kw are not preceded by anything, each keyword parameter has to have the forms (c #f) and (d 0), and (: c) expands to :c.  (Note: the a and b arguments aren't actually mandatory: they default to #f.)

In Guile, define/kw expands to define* rather than define, the keywords in define-kw are preceded by #:key, and (: c) expands to #:c.

Finally, the portable implementation (Chibi and everything else) also expands to a function definition, but its lambda-list is (a b . kws), and the body is wrapped in the Gauche/Chibi let-optionals macro, which does a runtime expansion of kws and binds the variables c, d.

Alas, there is no support for Racket, because there is no way for a macro to expand to a usable Racket keyword.  As far as I can tell, the same is true of Kawa.