Keyword and Optional Arguments in PLT Scheme

Keyword and Optional Arguments in PLT Scheme, Matthew Flatt and Eli Barzilay, 2009 Workshop on Scheme and Functional Programming.

The lambda and procedure-application forms in PLT Scheme support arguments that are tagged with keywords, instead of identiï¬ed by position, as well as optional arguments with default values. Unlike previous keyword-argument systems for Scheme, a keyword is not self-quoting as an expression, and keyword arguments use a different calling convention than non-keyword arguments. Consequently, a keyword serves more reliably (e.g., in terms of error reporting) as a lightweight syntactic delimiter on procedure arguments. Our design requires no changes to the PLT Scheme core compiler, because lambda and application forms that support keywords are implemented by macros over conventional core forms that lack keyword support.

As usual, a solid paper by the PLTers, this time on flexible argument passing. Making named arguments apparent at compile-time (by introducing keyword symbols that may not be used as ordinary values) seems right and enables some optimizations. There are also some nice Racket-specifics in there, such as the use of the customizable application form #%app, which - together with "applicable structure types" - allows the implementation of named arguments in "userland". The paper is rounded out by a performance evaluation and a description of similar facilities in other languages.

I think this is a very good design (and implementation technique) for named arguments. A facility [edit: syntax support] for receiving all named arguments of a function seems to be missing though - but it can probably be added in userland, too.

Comment viewing options

A facility for receiving all named arguments

is not missing. A syntax support for it is missing, though. Example from the paper:

(make-keyword-procedure
(lambda (kws kw-vals proc . args)
(printf ">>~s ~s ~s<<\n" kws kw-vals args)
(keyword-apply proc kws kw-vals args)))


Pure R5RS implementation of keywords

Aaron Hsu wrote down a simple implementation of keywords that's pure R5RS. Keywords are variables bound to a keyword object, but keyword objects are equal if they have the same names as opposed to being the same object, so it doesn't matter if they are proliferated.

The only downside, if it counts as such, is that you have to export the keywords as well as the procedure name from a module.

Interface evolution problems

I agree that there are modularity implications for keyword arguments. One such issue has to do with interface evolution.

Imagine you want to change a parameter name of an exported method that has multiple clients. If you simply change the names, then clients will break, so you don't want to do that. What else do you do, though? Without some care, you end up with a language where once a parameter name is exported, the parameter names can't be changed.

It looks like you need an additional language feature to support graceful changes to exported parameter names. One possibility is to support parameters having multiple names, some of which are deprecated.

This is the problem with all

This is the problem with all schema upgrade though. What do you do when a record field name changes? I don't see how this is any different.

A few admittedly naive responses. First, keywords might can be akin to symbols (minus CL style packages), globally, or cross-module, "interned" and simply evaluating to themselves. Second, one might have a system (as the paper describes) of specifying separate keywords and the names of the variables for each of the keyword arguments in a lambda list. Then one can change the variable names at will - just not the keywords. Third, is this any greater problem than the nature of Smalltalk method names, ala, "doit:to:me:like:you:do:" ?

- S.

Interface evolution problems

Scala does exactly this, parameter names can be deprecated.

def f(@deprecatedName('x) y: Int) = ...


will give a deprecation warning to callers using the parameter name x.

only?

Other downsides to keyword args as library:

* Slow.

* Not introspectable.

* Difficult to inline.

* Difficult to give warnings at compile-time.

Even the Racket mechanism is slower than CL's (and Guile's, fwiw) keyword args. Granted the Racket mechanism does have advantages that the CL method does not have.

analogy with case-lambda

Continuing to riff on this topic, it seems that all of the arguments for including case-lambda into a Scheme implementation also hold for integrating keyword arguments.

Racket's implementation is a library

Since you seem to be criticizing the idea of implementing keywords as a library, I think Racket's experience demonstrates that they can be all of these things. Although Racket doesn't provide compile-time warnings, the paper in the original posting describes how this could be accomplished. All of the other benefits are already features of Racket's system (speed improvements here likely require non-keyword-related optimizations).

I don't buy that library-based keywords are inherently slow. If a JIT doesn't optimize them away, it seems like a weakness in the JITer (e.g., a PIC should work).

I don't buy that you necessarily lose compile-time warnings; Racket is typically latently typed so, unless you look at the typed version, this doesn't seem to be the setting for examining it.

The introspection comment seems a bit off as well: you just might want library support for argument introspection to go along with the change in convention.

The problem of convention, especially for general human-readable tasks, seems problematic. The Racket setting of building the standard language in layers doesn't apply; it's more of a concern when third-party libraries include their own (incomplete) extensions without building in support for general debuggers etc. For this truly problematic case, it's not just keywords however -- almost any non-trivial automation technique is suspect.

In terms of bootstrapping, TCBs, etc. when implementing a language, the Racket approach seems compelling, including your concerns. I'm actually pretty surprised how much of a reaction this triggered!

Regarding speed, the paper

Regarding speed, the paper does show comparative speed measurements. PICs get complicated if you have later keyword arguments whose default initializers depend on previous ones, as Common Lisp does; and if you are extending your JIT to handle keyword args, that to me sounds like keywords are part of your base language, effectively anyway.

Some Lisps can warn if you pass an unknown keyword to a procedure.

I did not mean to criticise Racket's layering of keywords; rather Aaron's R6RS suggestion of keywords by convention. Racket has much better low-level tools like #%app, and it doesn't have the burden of standardization so it can use whatever it sees fit. To me it's almost the same as building in keywords at a low level.

Racket's keyword

The JIT support consists of just one function that the JIT knows how to inline, making up for the lack of cross-module inlining. Once cross-module inlining is available, the special case won't be needed, I believe.

I strongly disagree that using #%app and not worrying about standardization is like building something into the runtime. For example, this strategy means that we could experiment with other keyword argument designs, and keep those around for backwards compatibility. The same features that support this are the ones that support creating Typed Racket and Lazy Racket as libraries.

It is slightly tricky to articulate why this paper makes me sad for Scheme but I'll try.

Their way of doing keywords compiles down to a scheme dialect that happens to have a keyword type, but in which function application is its classic old self. That's sane, sure. But ....

Unfortunately, while the underlying application method is same as it ever was, the use of keywords involves a fairly hairy convention: passing a sorted list of keywords as the first argument and a sorted list of their bindings as the second. I get a little motion sick trying to think about how this relates to higher order functions.

As a result of that extra hair, and the high level semantics that motivate it, now suddenly application is haired up. There are two kind of application. One is complicated and is needed for this system of keywords. The other simple. You get weird artifacts like a (sort of, kind of) fixable inability to properly pass keyword arguments to a continuation. Wait, they are using a dialect where you can't use a lambda form to express a continuation unless more hair is first added? Really?

Now, let me pretend I just arrived from mars and I am handed that language and I have to explain it to a student. I can teach by pattern. Here is how you write a function that takes keywords. Here is how you write various higher order functions of a simple variety, ignoring keywords. I can hope the student doesn't ever notice the mismatched seams here or I can plan to quickly paper them over as they come up but, in the end, long gone is the simple elegance of function calls that were so simple to the discovered / invented language called Scheme.

I can understand adding a symbolic but self-evaluating and disjoint from symbols type to Scheme. I've done it myself. I can see making some very lightweight conventions about when and where to use that --- but by the time I am forced to drop a clear sense of how to apply functions, or else forced to adopt a very argument-rearranging complicated one ... something is lost.

And please don't object to me about efficiency. In the real world, Scheme has been amply overtaken mainly by languages for which efficient implementation is a much harder problem.

And please don't tell me that, oh, actually they are making it easier to compose functions in a higher order context because by the time you need that much hair for the kinds of compositions you want: keywords are not the mechanism you want in the first place.

Back in the early days of Guile I liked the idea of simply breaking the traditional syntax slightly. :foo is a keyword (say). I suppose #:foo is ok, too, if you must. Keywords self-evaluate. You can get the string name from a keyword or the keyword from a string name. They self-evaluate. End of story. Done. Let the programming community work out conservative ways to use those on an as-needed basis.

Scheme's fine. The kids are

Scheme's fine. The kids are all right ;)

More seriously, Racket's implementation choice is just that, an implementation (not interface) choice. The interface choice looks quite good. I've often wanted to be able to have positional rest args, keywords without values, etc.

re: *Scheme* is fine, if you can find one :-)

I think the main point where they open up the pandora's box of "that's no Scheme to me" is where they try to change the calling convention to one where the caller hides from an applied procedure the order in which keyword arguments are presented -- callers literally pass keyword arguments "differently" from other values. Once they've decided to do that they are pretty much committed to what they wound up with or something similarly hairy.

You're confusing levels.

You're mixing levels in the paper, and as a result you're reaching bogus conclusions about some imaginary purity that is lost.

To make things more concrete, an average efficient implementation of a rest argument is pretty hairy. When there are few enough arguments, they'll be left in registers, and when there are more they'll be pushed on the stack; when accessed in the function's body an efficient implementation will distinguish passing on the rest argument as is (as in the common idiom of getting a rest argument and passing it on to a different function), versus cases where the rest argument is actually used and needs to be reified as a list (and cached in this case, so further references get the same reified value). Things can get even hairier when the reified list is immutable by default for efficiency, since then the reification strategy should change when side effects are involved, and that brings into the game yet another sizable ball of hair. On top of this pile of hair there are some implementations that will use more customized representations that can accommodate efficiently the case of using an ad-hoc keyword facility (like the one you're sentimental for), for example some efficient plist lookup that works for the different variants of actual representation in the function call.

Phew. That was a pretty big pile of hair. More than that, as someone who has implemented Schemes, I'm sure that you're well aware of that hair. Yet for some reason *this* pile doesn't bother you, and the Racket pile does. Why is that?

Well, as someone who has taught Scheme for many years, I can see why all that hair won't matter: it's all a bunch of implementation details that at the user level you are completely unaware of. But guess what -- the hair that you're talking about is exactly the same kind. This whole thing about passing a first argument with a sorted list of keywords, and a second argument with a corresponding list of arguments, and then the rest of the arguments -- that's all stuff that deals with the implementation of this feature. If at some point in the future there is a better way of doing this low-level value pushing, then it will change. Even better: this technique for passing keywords/values can be done on top of a radically different representation -- for example, there are people who are pushing towards a single "options" argument with the keywords/values -- and that's very doable with this design exactly because the syntactic level is so independent from the implementation level.

Having said that, the Racket implementation is probably incomplete. What's missing is a convenient facility to get a hold of the keywords and values -- something equivalent to a rest argument. The two-extra prefix arguments are used for that now simply because there was no real need, yet, for a convenient layer. One option for such a convenient syntax will be familiar to you: for functions that have a dotted argument, have it contain a list of the rest values, *together* with the keywords and values -- possibly even by slapping them as a plist at the end of the normal rest values.

Why wasn't something like that done now? There are several reasons for that, and they are mostly a matter of taste. One reason is being able to reliably distinguish keyword arguments from non-keyword ones -- something that is not possible with the common plist approach since they are using the same transport by definition. Another motivation was to make keyword arguments truly independent of any order -- we want to be able to put keyword arguments anywhere in the function call without worrying about its position, and that's something that is much easier with the current implementation strategy. And this feature is used *very* often, btw -- see the #:exists example in the paper, which is much clearer than the usual only-at-the-end implementation; and using scribble is another very popular case where being order independent is a big win (and this is visible in CL systems that mimic xml by using keywords to represent attributes).

levels confusion

Thanks. It's nice to get your perspective.

I don't think I'm confusing levels. I think can say the same thing I'm trying to say without referring to the details you describe as implementation details.

Suppose that I want a procedure which accepts an arbitrary other procedure (OTHER), and arbitrary arguments. It essentially curries these, returning a new procedure which can be provided still further arguments. Finally, those two sets of arguments joined, it applies the procedure OTHER to them.

No matter what the implementation details, the step of combining these two instances of "partial arguments" is considerably more involved with PLT Scheme keywords. The reification of arbitrary arguments has become haired up. To write a fully general higher order function, I have to deal with that hair. If stylistically I often would write something less general, ignoring keywords - like in a classical scheme - then there are two distinct calling conventions in my system and it is non-trivially up to the caller to be sure which one to use.

You provide some rationales for this hair: arbitrary placement of keywords, non-ambiguity between keyword and other arguments, and order independence.

I don't think those rationales support the design. All of those goals can be achieved by convention, using a system in which keywords are self-evaluating objects and all else is as in ordinary Scheme.

Your rationales make sense if we say instead that you want to force programmers to not use position sensitive keyword, to not use order sensitive keywords, and to not allow any ambiguity as to keyword vs. other arguments. I don't have any religion that would support such restrictions, especially at the cost of a more complicated calling convention.

Well, anyway, I guess I'm supposed to understand that Racket is Racket and Scheme is Scheme (although I read on the racket site that "Racket is a Scheme" and "Scheme is a Racket"). I think in the historical context, regardless of branding efforts, I'm still allowed to be sad for Scheme :-)

Not too shabby, IMO

Suppose that I want a procedure which accepts an arbitrary other procedure (OTHER), and arbitrary arguments. It essentially curries these, returning a new procedure which can be provided still further arguments. Finally, those two sets of arguments joined, it applies the procedure OTHER to them.

[Using Common Lisp style function signatures] Assuming

• a facility &rest that gets me all [remaining] positional arguments as a list,
• a facility &keywords that gets me all keyword arguments as a dictionary,
• an apply that takes the function to call, a list of positional arguments, and a dictionary of keyword arguments,
• utility functions concat-lists and concat-dicts, that merge two lists and two dictionaries, respectively:
(define (procedure other &rest positional &keywords named)
(lambda (&rest more-positional &keywords more-named)
(apply other (concat-lists positional more-positional)
(concat-dicts named more-named))))


• The code

Here's the code to implement the two functions you refer to:

(define curry
(Î» (f . args)
(Î» args*
(apply f
(append args args*)))))

(define kw-curry
(make-keyword-procedure
(Î» (kws kw-args f . args)
(make-keyword-procedure
(Î» (kws* kw-args* . args*)
(keyword-apply f
(append kws kws*)
(append kw-args kw-args*)
(append args args*)))))))


re: the code

Right. The difference between the two and the fact I might want both in the same system helps to illustrate the complexity. I'm additionally saying that the additional hair is intrinsic to the problem even if we change the implementation details of how exactly make-keyword-procedure and keyword-apply work.

Re: levels confusion

• Please stop calling it "PLT Scheme", it's now called "Racket". "PLT Scheme" happened to be a temporary name that was used at the time the paper was written when we didn't know the actual name... Obviously, "PLT Scheme" is no more or less a "Scheme" than "Racket" is. (I'm only mentioning this because you keep referring to the name being a contention point.)
• Obviously there is more hair in combining the two argument lists. I'll give you even more: this example is one place where we need to access the actual argument passing protocol (as you can see in Sam's reply), therefore it's an example of a case where it would be nice to have a more convenient facility (at the user level) for doing so. But the thing is that these examples, where you append two argument lists are so rare, that there is no *practical* need for doing that -- yet.
• Now for your complaint: IIUC, it's about not having "append" as the only tool you need to combine argument lists... but that's expected when argument are not represented as lists. If this was something that is common enough to do, we'd obviously have some utility function for doing the job, and then things would become simple again.
• Again, assuming the above this is your complaint, then I don't see why you're singling out Racket's keyword-argument's facility as one with too much hair for your taste, when in fact the "simple" CL system (which is what you propose, I think) is suffering from more hair. To do the same with that system, you need to: take the two lists, find the keywords+arguments in both and split them accordingly, then combine the two results for the final argument list. This is more hair because you need to find where to split the input lists yourself.
• More than that, this solution in the simple system is less robust since there is no real commitment in these systems for the keyworded part to actually *be* a keyworded part -- it might be just some random argument list that looks like it has keyword, but really wasn't intended as such. So writing a *proper* keyword-respecting curry function with this system actually impossible. (And ultimately this is one motivation for a new keyword type -- one that signal such a commitment.)
• Put that in contrast to the Racket system, where you get back the desired robustness, and this is even with an added bonus of allowing keywords in arbitrary places. It certainly looks to me fine to conclude that the rationales of arbitrary placement and non-ambiguity support our design as one that implements it (and contradict the usual simple system which cannot do so).
• There is, however, some way of doing so with the "simple" system. The problem is that it makes the result be quite far from something that I'd use "simple" to describe. You need to have various "&foo" meta arguments that trigger different pre-parsing for the actual argument list, and the hair piles up. In fact, our previous system, which is mentioned in the paper, did just that -- use the "simple" system of passing argument lists, and go on from there. The result was far from simple. It was powerful though -- it went all the way into a specification that could parse different keyword/argument parts at different parts of the input, similar to the use of "--" to separate levels of command line flag parsing. But nobody used any of that. (That's in addition to making things very slow, something that is usually not seen because usually such systems end up with the truly simple implementation, the one that is inherently ambiguous and unreliable.) You can see it here, and the implementation is in the racket tree, still.
• You're also right that it's important to look at what happens when people write "something less general, ignoring keywords" -- which I take as the simple implementation that uses "append". In both systems it's possible to do that, and the code would be identical. In both systems, the result would work fine as long as no keywords are used. But the interesting question is what happens in both systems when you try to use the resulting curry with a keyworded function and use it with some. In our system, this will not work -- when keywords are not specified, the resulting function will refuse to accept keywords, and you'll get an error. In the "simple" system you don't get any errors, but unless you happen to do things in a particular way (eg, the first call has some keywords and the second is all keywords, or the first call has no keywords and the second has some) you get nonsense results. IMO, that kind of nonsense is dangerous, as seen by the description of such a curry function -- and the fact that the lazy programmer who wrote the simple curry will not mention the pitfalls, leaving the problem to be discovered by client programmers.
• Finally, programmers are not forced to use any of this. Since the "simple" system is mostly just based on conventions but otherwise uses plain rest lists, then there is no problems in doing just that

Re: Re: levels

Regardless of names, the future of Racket is in part a story of the fate of Scheme. No offense was meant by using the same name for the language that is used in the paper under discussion.

For the record, I do not advocate the CL keyword-handling stuff. If I started with Scheme and wanted keywords, I would simply add a self-evaluating keyword type - disjoint from symbols - and leave it at that.

More specifically, it looks to me like the Racket hackers made up a wish list of features to support a certain style of using keywords, found that they did not fit Scheme's simple (and very "of the essence") rules for argument passing, and valued the style of keyword arguments over keeping procedure calls simple. I don't suppose that there is any absolute right or wrong there but I think in my experience and in my view: the particular style of keyword usage just isn't worth it, and the simple model of procedure calls is vastly more valuable. Mucking with things like apply as you've done sends shivers down my spine. PLT folks' mileage palpably varies. I get that. That's fine. I don't understand why you seem to be taking such offense at what I said and what I still stand by. So be it. No offense was intended. I simply think it interesting (and "sad for Scheme") some of the details of what Racket has done here. And, yes, I think there is a case against the design decisions but I wasn't trying to decisively make such a case only to point out its existence and relation to Scheme (from which Racket forked, so to speak).

Gestures

Regardless of the technical merits, I think what generates misunderstandings and offense is your use of phrases like "sad for Scheme" and saying that there's a case against certain design decisions without wanting to make that case. If you gesture in the direction of not liking our work without backing it up, that's your right, but you can't really expect us to like it.

re gestures

If you gesture in the direction of not liking our work without backing it up, that's your right, but you can't really expect us to like it.

I explained what I don't like about it. The added complexity to argument passing and the awkward situation of having two kinds of procedure call (one of which reduces to the other somewhat baroquely) seem like an awfully steep trade-off for the keyword argument features obtained. It's a striking choice in a "descendant of Scheme" because of the role Scheme's simplicity in these areas have been so important to analyzing programs, understanding them, writing interpreters, etc.

A specific example might help to illustrate: In Racket: renaming a keyword can change the meaning of a program in surprising ways (because it can be similar to changing the order of arguments). I wonder in what situations that might complicate writing Racket->Racket transforms or even some uses of make-keyword-procedure and keyword-apply.

For the record, I do not advocate the CL keyword-handling stuff. If I started with Scheme and wanted keywords, I would simply add a self-evaluating keyword type - disjoint from symbols - and leave it at that.

Ah -- in that case you don't want keyword arguments at all, at least not a concrete implementation. But you do say that you'd add a new type, which must be something that is intended for implementing keyword arguments -- you just don't want to commit on any specific mechanism? In other words, you want to do the minimal work to enable a user-implementable keyword argument feature, but the actual implementation is left as an exercise to the user. In such a world, if I want keyword arguments I just implement something myself, usually half-assed, and always incompatible with Some Other Hacker's own keyword argument implementation. In such a world you still get to write your nice single-apply version of curry but each of us (me and Some Other Hacker) can't use it with our keyword argument facilities, and neither can you use our keyworded functions in other higher order functions. And since I need curry, I end up supplementing my code with my own (my-)keyword-aware curry; Some Other wants it too, and does his own version; and because I want to use his libraries, I end up writing some glue code that translates his keyword/arguments into mine.

This would be a good point to stop for a minute and reflect your sentiment from the other side and be sad for "Scheme": the language of useless minimality where in most implementations you end up re-implementing your own trivialities over and over. It happens with a record implementation, an object system, iteration forms, reader extensions, FS interface, module system, syntax system, etc etc. Personally, I prefer to just choose something and go with it -- I enjoy an occasional high-level discussions, but at some point I want to actually get some hacking done. (That is, hacking new code, not re-invent stuff.)

BTW, it's worth noting what should be obvious for the Nth time. As far as the Racket C core goes, what you're talking about is exactly the change that was done: a new "keyword" type, and nothing else. The keyword facility that is described in the paper is all in user-land racket code, and other implementations can be (and have been) implemented. The specific keyword system from the paper is essentially used by-convention. The Racket world even has a place where you would feel comfortable: the core language where apply is the same thing you want -- and the Racket meta-language facilities are good enough that making such a language for you is very easy (but not needed here, it's what the "mzscheme" language implements). Your curry will not work right with keyworded functions, but there's nothing special in the described system in this regard, since your curry will not work with any other system. It's even easy to create a language with interoperability tools for the keyworded side, so you could write it simply (but that's not needed either, since you don't want to deal with any kind of keyword calls).

More specifically, it looks to me like the Racket hackers made up a wish list of features to support a certain style of using keywords, found that they did not fit Scheme's simple (and very "of the essence") rules for argument passing, and valued the style of keyword arguments over keeping procedure calls simple.

And as we've finally concluded, this will happen with any keyword argument system -- Racket's, CL's, or a half-baked home cooked one. Your suggestion is to use no keyword argument system at all, but that obviously doesn't solve any problem that keyword arguments address.

I don't suppose that there is any absolute right or wrong there but I think in my experience and in my view: the particular style of keyword usage just isn't worth it,

...and in the collective Racket experience it is worth it. Also in the collective experience of some other implementations (Guile and CL included).

I don't understand why you seem to be taking such offense at what I said and what I still stand by. [...]

No offense taken. Really. If you had clarified from the start that what makes you "sad for Scheme" is the essence of keyword arguments -- of any kind, and in any implementation -- then I wouldn't have bothered replying in the first place.

If it hurts when you do that...

I don't see any good reason why every library should use keywords in the same way. Sometimes order should be insignificant, other times no. Sometimes keywords ought not be repeated, other times that should be permitted. And so on. The lack of uniformity among libraries in this area is neutral with respect to whether the resulting aggregated system is well designed or not.

The "currying problem" is intrinsically tricky for keywords, regardless of the exact rules we make for them. In many cases, we don't care much: the functions where we use keywords either curry normally or are unlikely to be curried. Not all cases, though. So what then?

That's when I would say (from a Scheme point of view), that rather than complicating the rules of function application, instead "if it hurts when you do that, don't do it." Consider defining a new, non-applicative data type which can accumulate a keyword->value mapping (along with other state).

This would be a good point to stop for a minute and reflect your sentiment from the other side and be sad for "Scheme": the language of useless minimality where in most implementations you end up re-implementing your own trivialities over and over. It happens with a record implementation, an object system, iteration forms, reader extensions, FS interface, module system, syntax system, etc etc. Personally, I prefer to just choose something and go with it [....]

As do I but choices have to be weighed and compared.

...

I don't see any good reason why every library should use keywords in the same way.

Exactly: you don't. Instead, you encourage no implementation, and this viewpoint is exactly why I'm sad for "Scheme".

Sometimes order should be insignificant, other times no. Sometimes keywords ought not be repeated, other times that should be permitted. And so on.

Yes, but given that they are useful, I prefer having some convention over nothing at all, so that people can actually share code that uses keyworded functions. That's obviously different from your preference for making no choice (and therefore making sharing much more difficult). Note that whatever implementation I choose, I'm not closing off any other alternatives -- if for some reason you want keywords that are repeated or whatever, you can still implement that, the only difference in this case would be going against a popular convention or going against no convention; the code will be the same.

That's when I would say (from a Scheme point of view), that rather than complicating the rules of function application, instead "if it hurts when you do that, don't do it."

And since any solution will complicate the rules of function application, then what about the hurt on the other side: of not having any convention?

Consider defining a new, non-applicative data type which can accumulate a keyword->value mapping (along with other state).

We did. (In fact, I've referred to this implementation strategy earlier.) But the result is still complicating application rules, so you oppose that too -- why are you asking this question then?

As do I but choices have to be weighed and compared.

Right. Here's a quick summary: we have avoided the issue for a while, we then implemented keywords with the popular CL-like approach, then extended it with things that were needed, then considered and finally implemented a different system, then switched considerable chunks of code to use it as well as using it heavily in more code, and then we reflected on the resulting system when compared to the previous one and when compared to other systems in terms of robustness, convenience, flexibility, and efficiency, and summarized that in the paper. I think that qualifies fine as "weighed and compared".

You, on the other hand, express some vague "sadness for Scheme" that turns out to be "sadness for any implementation of keyword arguments". Other than that, you made no concrete point for a different implementation, instead, you're explicitly advocating nothing. if there was supposed to be a real point, I have definitely missed it.

tiresome

This is getting tiresome. I will say that I think you have repeatedly given the least generous and somewhat inventively worst possible readings to things I've written, attributing to me views I don't hold and didn't express, and then angrily and deridingly replying to the straw-man you have constructed.

Racket comes from a Scheme context. It's "fork" from Scheme is a significant event in the history of Scheme. Your paper is a striking example, in my view, of what that fork entails. I offered up a Scheme view. I tried to explain why the Scheme view is viable. In return I get stuff like this:

You, on the other hand, express some vague "sadness for Scheme"

Nice chatting with you.

re: tiresome

• I did not take any post you made as an "attack".
• I have done my best to understand what it is that you're complaining about, the only points that I have seen are the vague grumble against complicating function application, and later you clarified that you want no keyword argument facility other than rudimentary support for making one up (a new builtin type), since they all complicate function calls. If that was "inventive" or un-generous, or attributed to you a view that you don't hold, then the way to resolve it is very simple: just specify which keyword argument facility you think should have been used. Better: write the curry code for that suggestion, so there's something concrete to compare against the sample that Sam posted earlier.
• Yes, you can consider Racket a fork from "Scheme", but that's not different than considering any other practical implementation of Scheme a fork -- most of them even complicate the function call protocol by adding keyword arguments...
• My use of "vague" was not intended as any kind of an insult -- its purpose is to point at the fact that I have seen no technical point you made. Like I said, feel free to remove the vagueness by making a concrete suggestion for some system that doesn't suffer from complicating the function call mechanism.

enough. re: tiresome

I have done my best to understand what it is that you're complaining about, the only points that I have seen are the vague grumble against complicating function application,

I don't think that there's anything vague about the loss of uniformity and simplicity. You've complicated reasoning about programs, writing higher level functions, writing program->program transforms and so forth. As I pointed out to Sam, you even wound up with a system in which globally renaming a keyword to a fresh name (which isn't quite but which is a lot like alpha-renaming) can change the meaning of a program and turn correct code into incorrect code. Just renaming the keyword! (And, no, I don't mean break correct code in trivial ways like changing what is printed or changing the validity of converting a string to a keyword etc.) I've pointed out the ragged edges (like no keyword support in continuations) and acknowledged that those can in some (vague) sense be "fixed" but I point out the present deficit as evidence that the complexity has gotten out of hand (breaking the identity between continuation invocation and ordinary application is startling, even if its under the cover of "we can get around to fixing that").

If that was "inventive" or un-generous,

What is inventive and ungenerous is assuming that I'm advocating for the CL system and then spending many paragraphs refuting that position. Similarly assuming that I'm in favor of or at least uncaring about "half-assed" solutions for keyword parsing.

just specify which keyword argument facility you think should have been used.

I've tried to make clear that the alternative view here is to lower the goals. If you're programming style seems to want particularly hairy uses of keyword arguments, look for alternatives. If it needs only simple ones that are easily handled in ad hoc ways, they're very handy.

The way you folks have done keywords, what you wound up with is a kind of hybrid between a procedural interface and a dictionary abstract data type. One can make a kind of computational calculus that uses dictionaries to describe argument passing but as the frayed ends and the unavoidable complexity of your approach in Racket shows (contrasted with keyword-free Scheme), conflating these two things makes something kind of muddled.

My use of "vague" was not intended as any kind of an insult -- its purpose is to point at the fact that I have seen no technical point you made. Like I said, feel free to remove the vagueness by making a concrete suggestion for some system that doesn't suffer from complicating the function call mechanism.

I've done so about three times now. Where you seem to get hung up on seeing it - it looks like to me - is that you see me specify that mechanism and you think "Wait, that doesn't solve problems X, Y, and Z that were our goal!" And then you just don't hear me when I try to say, again and again, "Right. I'm saying that from a Scheme view and in my experience, X, Y, and Z are not goals so valuable as to justify such divergence from the simplicity of Scheme procedure calls."

I suppose that's not a "technical" point but in that same sense, rationales about "half-assed" code and so forth aren't "technical".

Yes, you made it clear that

Yes, you made it clear that you're not suggesting CL keywords, that was a mistake I made when I thought that you actually had an opinion re a solution rather than object to all solutions. And given that you don't see a problem that justifies any keyword library, you'll be forever against all of them. The only oddity left is why you chose to vent your general objection here, when various keyword systems have been around for decades.

(And BTW, re your "hybrid between procedural and dictionary", I can only repeat that you're still mixing levels. Naturally, you will disagree.)

And given that you don't see a problem that justifies any keyword library,

Not what I said. Not close.

you'll be forever against all of them.

um....

The only oddity left is why you chose to vent your general objection here, when various keyword systems have been around for decades.

Because it's interesting to regard your paper in its historic context vis a vis Scheme.

(And BTW, re your "hybrid between procedural and dictionary", I can only repeat that you're still mixing levels. Naturally, you will disagree.)

The "dictionary" aspect is inherent in the high level semantics of your version of keywords, regardless of implementation details.

You object to any

You object to any complication of function application rules. All keyword argument facilities do that. Therefore you object to all of them.

At this point I'd ask if there's anything wrong, and I'd ask for a concrete code example. But I already did both, and you didn't say what's wrong, and didn't post any concrete code (for curry, your own idea for comparison). This is the same vague line that you started with, and it seems now that except for rhetorics, there's nothing else that will come out of this non-discussion. And it's not even an honest rhetorics at that, since you decide to become sad for Scheme because of *this* particular keyword system, when the problems you complain about exist in *all* of them. (Of all implementations, you ironically do this for Racket, that even went as far as renaming itself to avoid such confusion about "Scheme".)

At least this thread is an effective way of communicating the sad state of the "Scheme" world. Please continue it to make it even clearer.

At least this thread is an

At least this thread is an effective way of communicating the sad state of the "Scheme" world.

Speaking of which, I don't think that aspect of the discussion is a very interesting topic for the more general PL readership of LtU - it'd be better hashed out in a Scheme forum somewhere. Although I suspect that dispute could only be settled with a cagematch.

I genuinely have no idea what you think the problem is. There's nothing wrong with higher-order functions in this setting. Everything works just fine. Can you give an example of something that you think is "simple" either in Scheme without keywords, or in the CL-style keyword system? I expect that modulo the #:foo/:foo/foo: distinction, everything looks exactly the same in Racket.

re: What are you talking about

There's nothing wrong with higher-order functions in this setting.

You can sense that they've gotten oddly hairy from two examples: First, try to imagine giving a plain english account to a Scheme newbie of what an implementation of the new apply must do. Compare that to the description without this particular form of keyword support. Second, note how - although it an be "fixed" - call-with-values doesn't work with this system. The "fix" is also difficult to explain to a newbie since it will amount to defining the original call-with-values and then kind of shuffling around and looking askance while explaining that the actual call-with-values is in turn a wrapper around that.

I regard both of those as huge sacrifices of the elegant simplicity that originally distinguished Scheme from other lisps. In this case, they are sacrifices in service of an expressively constricted take on keyword arguments.

Can you give an example of something that you think is "simple" either in Scheme without keywords, or in the CL-style keyword system? I expect that modulo the #:foo/:foo/foo: distinction, everything looks exactly the same in Racket.

For discussion purposes, let's just use #:foo (personally, I prefer to drop the hash mark, but...).

The keyword? type is a type of self-evaluating values, disjoint from the standard list of disjoint types.

If you like, presume that there are procedures string->keyword symbol->keyword and their inverses keyword->string and keyword->symbol.

Two keyword values are eq? exactly when the corresponding symbol is eq?. For calls to keyword->string the string name of the corresponding symbol is returned. For multiple calls, the strings returned are at least string=? though not necessarily eqv?.

No changes are made to the definition of application. Keyword values are passed, in all cases, as any other argument.

Parsing of keyword values must be explicitly specified in the definition of each procedure that recognizes them for which purpose it is convenient to define syntax for various kinds of pattern matching.

The end.

Note that this method does not attempt to address some of the issues which the PLT Scheme system seems to wrestle with. For example, the order of keyword arguments among other arguments is significant in this simpler system. In this simpler system, keyword arguments may take 0, 1 or more corresponding arguments. A single keyword may be meaningfully repeated with multiple possible interpretations. apply does nothing special with keyword arguments. call-with-values behaves normally and unsurprisingly.

As a consequence, this simpler system is at once expressively less constricted, but also can give rise to "inconsistent" use of keywords among various parts of a program. This simpler system discourages some idioms that cry out for an apply that works like the one in the paper. This is a programming style problem, not a language design problem. If it hurts when you try to code that way, don't.

In particular, if you want something like keyword designated arguments, where the order of presentation is not significant, and where it is easy to override or supplement a received list of keywords -- First recognize that this is a hairy desire and that keywords are very useful when used more simply; Second devise a data structure for all of those parameters and a convention (such as always passing it as the first argument, or as the argument to a simpler form of keyword).

I'm still confused

You can sense that they've gotten oddly hairy from two examples: First, try to imagine giving a plain english account to a Scheme newbie of what an implementation of the new apply must do. Compare that to the description without this particular form of keyword support. Second, note how - although it an be "fixed" - call-with-values doesn't work with this system. The "fix" is also difficult to explain to a newbie since it will amount to defining the original call-with-values and then kind of shuffling around and looking askance while explaining that the actual call-with-values is in turn a wrapper around that.

I regard both of those as huge sacrifices of the elegant simplicity that originally distinguished Scheme from other lisps. In this case, they are sacrifices in service of an expressively constricted take on keyword arguments.

I think either you've misunderstood the paper, or I'm misunderstand you. For example, no changes to call-with-values are needed. This program:

(call-with-values (lambda (x y) (+ x y)) (lambda () (values 1 2)))

doesn't involve keywords, doesn't use any special calling conventions, and works the same as it did before. What do you think is different about call-with-values?

apply is different and more complex to implement, I agree. However, it's still just as easy to explain in the absence of keywords. Whether this is a problem is a language design taste question, but not a really central one to the meaning of Scheme.

re: still confused

doesn't involve keywords, doesn't use any special calling conventions, and works the same as it did before. What do you think is different about call-with-values?

Look at the end of section 2 in the paper:

PLT Scheme does not extend case-lambda to support key-word or optional arguments; the extension would be straightforward, but there has been no demand. Similarly, continuations in PLT Scheme do not support keyword arguments. Extended variants of call-with-values, values, and call/cc procedures could support keyword results and continuations that accept keyword arguments. We have not tried that generalization, but an implementation could use continuation marks (Clements and Felleisen 2004) that are installed by call-with-values and used by values and call/cc to connect a keyword-accepting continuation with its application or capture.

That's a symptom of the design decision to implement keyword arguments by hairing up the calling convention.

There is another symptom here, in section 4:

To allow procedures with optional keywords to be applied through the core application form, the implementation relies on a second PLT Scheme facility that predates support for keywords: applicable structure types. When the core application form encounters a value to apply that is not a procedure, it checks whether the value is an instance of a structure type that has an associated application operation (which is itself represented as a procedure). If so, it uses the associated operation to apply the structure to the given arguments. For example, another way to create noisy procedure applications is to wrap the base procedure in a traced structure:

(define-struct traced (f)
#:property prop:procedure ; => applicable
(lambda (t . args)
(let ([f (traced-f t)])
(printf "âˆ¼s\n" (cons f args))
(apply f args))))
(define traced-cons (make-traced cons))
(traced-cons 1 2)


Internally, the keyword-handling part of a procedure is represented by a core procedure that accepts a list of keywords, a list of corresponding values, and then the by-position argumentsâ€”just
like a procedure given to make-keyword-procedure. This internal representation is wrapped in an applicable structure, where the application operation (which is used by a non-keyword application form) calls the internal procedure with empty keyword and keyword-value lists. The application form with keywords, meanwhile, extracts the internal procedure and applies it to non-empty keyword and value lists. The list of keywords is always sorted alphabetically, so that the supplied keywords can be checked against an expected set without sorting or searching when the internal procedure is called. The internal procedure is not directly accessible,since it is wrapped in an opaque structure

In contrast, in the system that I'm describing, you can pass keywords to a continuation using the standard call-with-values and you certainly don't need to explain apply in terms of "applicable structures".

However, it's still just as easy to explain in the absence of keywords.

It would seem not since they wind up having to explain the keyword-using apply in terms of the simpler Scheme apply

Whether this is a problem is a language design taste question, but not a really central one to the meaning of Scheme.

I agree that this is a taste question to an extent. I'm not sure what you mean by "the meaning of Scheme" but to me it is a language whose definition, implementation and use embodies the once-startling parsimony of the early papers. A language with these keyword features and the oddities of the rule for application is certainly definable in Scheme but is so far removed that I think much of the value of the original Schemes has been washed away, even though the insights and practices of those early dialects still have value today (modulo minor clean-ups).

If Stroustrup - by way of analogy - had called his new language "C", I'd have felt similarly sad for "C". Because he kindly called it "C++" I can tell people of my interest in "C" without causing confusion. I can happily articulate the virtues of C which C++ sacrifices and the additions in C++ which highlight idioms not well supported by traditional C.

This language at hand is called PLT Scheme. I feel sad for Scheme.

Racket

As I said, call-with-values is not changed, and works just as before. The addition of keywords doesn't break the calling convention that it uses, and it's just as easy to explain as before.

Second, "this language at hand" is called Racket, for reasons which should by now be obvious. To quote the explanation for the name change: "PLT Scheme is no minimalist embodiment of 1930s math or 1970s technology." I believe that to be a good thing; your feelings may be otherwise.

Of course, the required language extension truly is minimal: just a new form of data (not even self-quoting). With a powerful macro system, the sky is the limit.

Missed the news?

This language at hand is called PLT Scheme.