Wrapping up SRFI 177: Portable keyword arguments Lassi Kortela (22 Feb 2020 00:48 UTC)
Comparing current and potential future syntaxes Lassi Kortela (22 Feb 2020 01:25 UTC)
Re: Comparing current and potential future syntaxes John Cowan (22 Feb 2020 22:17 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments John Cowan (22 Feb 2020 21:22 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments Lassi Kortela (22 Feb 2020 22:17 UTC)
Hygiene and allow-other-keys Lassi Kortela (22 Feb 2020 22:39 UTC)
Re: Hygiene and allow-other-keys Shiro Kawai (22 Feb 2020 23:48 UTC)
Re: Hygiene and allow-other-keys John Cowan (22 Feb 2020 23:52 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments John Cowan (22 Feb 2020 23:40 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments Shiro Kawai (22 Feb 2020 23:58 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments Lassi Kortela (23 Feb 2020 00:12 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments John Cowan (23 Feb 2020 01:03 UTC)
Required keyword arguments Lassi Kortela (23 Feb 2020 07:39 UTC)
Re: Required keyword arguments John Cowan (23 Feb 2020 16:35 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments Marc Nieper-Wißkirchen (23 Feb 2020 08:33 UTC)
Re: Wrapping up SRFI 177: Portable keyword arguments Lassi Kortela (23 Feb 2020 10:09 UTC)

Re: Wrapping up SRFI 177: Portable keyword arguments Lassi Kortela 22 Feb 2020 22:17 UTC

> However, I'm wondering what good lambda/kw actually is, since there is
> not and cannot be apply/kw, and plain apply forces all the keyword
> values to the default.

call/kw is apply/kw:

(define (just-for-fun thunk)
   (call/kw thunk 1 2 :c 3 :d 4))

(just-for-fun (lambda/kw (a b &key c d) (list a b c d))) ; => (1 2 3 4)

I don't know whether this is useful. I just did it because it's the
least deviation from ordinary lambda.

Another thing is that passing around a keyword lambda for use as a
higher-order function is nice, even if it gets default values for all
its keyword arguments that way. It's still possible to call it with
keyword arguments from other contexts. This arrangement makes it
possible to add keyword arguments to existing procedures that are
already being used as higher-order functions. This is a good example of
the kind of situation where I want people to be able to add keyword
arguments without a lengthy and difficult analysis that "will this break
some existing code"?

> It sort of reminds me of not having
> generic-function-lambda in CL; what's the point of an anonymous generic
> function?

An anonymous generic function would be like case-lambda, but casing on
argument types instead of the length of the lambda list.

>     Common Lisp compilers can optimize keyword calls,
>
> But probably not when the procedure-with-keywords is a lambda.

In principle, it should have more to do with whether the particular
lambda being called is known at the call site or not. If it's known, it
can be inlined like any other lambda.

> &key is standard in most Lisps and does not step on native keywords.

Stepping on native keywords in the lambda list shouldn't be a problem.
However, favoring &key because it's more standard is fine.

DSSSL and SRFI 89 use #!key, but if you want SRFI 177 in R7RS-large
after all, using something other than #!key will make it possible to
avoid mandating extra syntax in the language.

> ObDigression:  ISLisp does not require runtime keyword objects to exist,
> because they are used only as syntactic keywords in various special
> forms/macros (no explicit distinction in the spec)  and there is a fixed
> list,  namely :abstractp, :accessor, :after, :around, :before, :boundp,
> :generic, :initarg, :initform, :metaclass, :method, :reader, :rest,
> :writer.  However, &rest can be used instead of :rest.

Interesting to know. Sounds similar to Kawa and Racket.

> Anyone who uses both keyword arguments and normal identifiers that look
> like keywords deserves to lose.   At least the portable use of keywords
> is confined to these two macros.

I agree that it's unwise. I think Marc's point was more about permitting
a simple language definition without hacks than about what people should
use in practice.

> Getting Scheme implementers to accept new lexical syntax is one of the
> most difficult things to arrange for, because it often requires pulling
> apart hand-coded parsers.  CL-style readtables make it straightforward,
> but only Racket, Gambit, Chicken, and Guile (through Guile-Reader) have
> them.  So I am very much against this.  Indeed, it is why I have
> postponed all lexical proposals to the very end of R7RS-large work, and
> treat any lexical syntax in existing SRFIs as merely advisory until then.

Sorry, I must have missed the memo. In that case you can't have keyword
arguments in R7RS in such a way that they easily permit supporting
hygienic keywords with the same syntax as non-hygienic ones. All the
non-hideous syntaxes for mixing hygienic and non-hygienic keywords rely
on using keyword objects for non-hygienic keywords, and treating
ordinary identifiers in "keyword position" as hygienic keywords. If
there are no keyword objects in the language, then ordinary identifiers
in "keyword position" (or ordinary identifiers whose names at make them
look like keywords macro-expansion time) have to stand for non-hygienic
keywords.

> IMO the whole point of keywords is that they are not hygienic: they have
> absolute meaning.  I'm open to being convinced otherwise, of course, but
> nobody has done even that, still less implemented them.

I think there's some merit to the hygienic keywords approach, but it's
quite experimental whereas non-hygienic keywords have been common for
decades. In practice, having to explicitly export and import keywords
would probably cause confusion to most people and would obviate much of
the benefit of keyword args (which is convenience - they are a more
convenient way to do the same thing you could do by defining a record
type). Since keywords are all about programming-in-the-large, working
well with other people's unfamiliar code is all-important. A
non-hygienic keyword "does what it says on the tin" so it's good from
that point of view as well. If a hygienic keyword is imported, what if
you accidentally imported it from the wrong library, or need to import
conflicting keywords with the same name from two different libraries?

>     * Since SRFI 177's main goal is compatibility with existing Scheme
>     keyword systems, it will use the syntax (call/kw foo 1 2 :d 3 :e 4).
>     Here, :d and :e can be either identifiers (in any Scheme implementation)
>
> Only if it does not automatically treat :d as not an identifier.

The call/kw macro needs to have a check like:

(define (parse-keyword elem)
   (if (keyword? elem) elem
       (and (symbol? elem)
            (let* ((s (symbol->string elem))
                   (n (string-length s)))
              (and (not (= 0 n))
                   (cond ((char=? #\: (string-ref s 0))
                          (string->keyword (substring s 1 n)))
                         ((char=? #\: (string-ref s (- n 1)))
                          (string->keyword (substring s 0 (- n 1))))
                         (else #f)))))))

(parse-keyword 'foo)   => #f
(parse-keyword ':foo)  => foo:
(parse-keyword 'foo:)  => foo:
(parse-keyword foo:)   => foo:

That will make it work in all implementations.

>     call/kw would scan for those literal symbols at read time,
>
> At macro expansion time, presumably.

Yes, that's better terminology. call/kw would not eval anything to
decide at runtime what is a keyword and what is not.

> On the contrary, I believe that SRFI 177 is the *only* hope of getting
> keyword arguments into R7RS, politically and logistically.  It's not
> portable, but it's one of the most portable bits of non-portable code
> around, thanks to all the work you have put into it.
>
> Not only that, but I want to get it in ASAP so that future SRFIs can
> define procedures that take keywords.

Agreed. We already have several draft SRFIs and pre-SRFIs that could
benefit.

I feel a bit uneasy about "rushing" a new language feature in. But it's
only new in standard Scheme - the territory has been thoroughly explored
by many Scheme implementations, other Lisp dialects and other languages.

If there were obvious alternative, I'd advocate going slower and
checking them out. But what would those be?

- Passing keywords as alists/hashtables is an obvious hack that is
already being done too much IMHO, including in my own code. I've never
felt good about it.

- Defining new record types for individual procedures is just the kind
of verbosity that Lisp and dynamic typing are supposed to help avoid.

- Defining a macro that parses its own keywords instead of a
standardized keyword procedure is definitely not simpler or less
error-prone.

- Defining a little domain-specific language made of simple combinators
is laudable, but requires a lot of thought and design. It's not a
"set-and-forget" kind of thing like keywords are. IMHO
programming-in-the-large (especially application programming) is not
tenable if so much thought has to be put into every component of the
application. Even if Scheme is beautiful, there are times in a large
project when you just have to get a job done and move on.

>     * default values for keyword arguments
>
> An alternative would be to create a singleton object, use that as the
> default, and expose a trivial predicate keyword-default-value?.  But #f
> is probably good enough: Scheme conventionally uses it as a null anyhow.

Gambit uses a custom (macro-absent-obj) a lot in its internals. The
resulting code so is verbose that it's not really suitable for user
code, and indeed Gambit doesn't even advertise it as a tool for users.

I used to admire Common Lisp's comprehensive lambda list syntax. Then I
actually used it extensively and came to the opposite conclusion. I used
to think e.g. that supplied-p parameters for optional and keyword
arguments are a good thing, as are custom default values. Now I think
both are examples of over-engineering. In large programs, or when
switching between different programming languages instead of dedicating
your whole programming life to Common Lisp, you keep forgetting how the
things behave. Using #f as a default for everything is extremely simple
to remember and composes naturally.

I have nothing against having more complex stuff in Scheme, but for my
own code the kind of simplicity currently in SRFI 177 is actively desirable.

The nice thing about keyword systems is that interoperability is
achievable, so I can keep using a simple lambda/kw and still call other
people's code written using a complex one.

>     * mixing keyword arguments with positional optional arguments
>     * mixing keyword arguments with a rest argument
>
> IMO both of these are bogus things to do.  If you add a new optional
> argument to a CL function, for example, you break all existing calls on
> that function, because the keyword becomes the value of the new argument
> and everything else is just wrong after that, but at least you probably
> get an error.  Add *two* new optional arguments, and the keyword
> mentioned first just disappears.  CL programmers have to filter out the
> keyword-value pairs from &rest by hand if they use both.

I agree with your judgement, but others want them. Since these features
are all opt-in and interoperable with the simpler system, I have no a
problem with them.

>     * allow-other-keys (collecting unknown keywords into a list)
>
> This is the only real nice-to-have that I see from your list, but
> certainly one can do a lot without it.

Agreed, and agreed.

> Another good thing about SRFI 177 is that it finesses CL-style vs.
> Racket-style keywords, which I would like to avoid debating if possible.

Thanks. It was borne out of necessity. IMHO any keyword system that
isn't fully compatible with all the existing ones is not worth
attempting. One of my favorite things about Scheme is that it's easy to
fit in your brain. It doesn't have countless esoteric features like CL.
We can only preserve Scheme's advantage if we fold incompatible
variations in language features into common subsets. R6RS and R7RS did
that admirably with module systems. We can do the same with keywords.