Re: Procedures with keyword arguments should be macros not procedures Lassi Kortela 17 Oct 2019 13:37 UTC
> Thank you very much for your detail reply. I will comment between the > lines below: Thanks for taking an interest! It's important for Scheme to have some fully portable way to do keywords arguments, whichever way it is. > Sure, it will only make a difference if the procedure in question is > called several times and the work done in the procedure is relatively > small compared to the dispatch overhead. The thing is that procedures called very many times tend to some quite specialized kind of work, so they don't usually need to have many and parameters of different kinds and hence do not benefit that much from keyword arguments. They are also typically hidden in the internals of a module, so the interface can be refactored as requirements change. Keywords are great for interfaces that have to remain backward compatible, so refactoring is not an option and you have to add optional stuff on top of the things that are already there. Common Lisp's sequence functions are a bit of an oddity: they are called a lot, and are quite specialized, yet they are _also_ part of a public API that cannot change. Perhaps some 3D graphics package or other numerical package could have similar procedures to do complex math, taking many arguments but also need to be called a lot in rapid succession. Maxima is written in Common Lisp; does it use keyword args a lot? > It won't matter when, say, an HTTP client or server is being > initialized but it will matter a lot if people want to use the > primitives defined by SRFI 177 in cases where speed matters (see also > below). Sometimes, this price in form of slower code has to be paid > (e.g. when you want to use a powerful object system like CLOS), but in > the case of keyword arguments my point is that we do not have to pay > this price thanks to the power of Scheme macros. That is true. Maybe it would be best if users can define both keyword macros and keyword procedures. Would the interface then be too complex to understand? It might be, since many implementations also have their own keyword argument syntax. I tried to make the "simplest thing that could possibly work", or "minimum viable keyword arguments" :) So that the total complexity of Scheme (i.e. the complexity that has to be kept in programmers' heads) would increase by the smallest possible amount. If the native keyword syntaxes went away with the introduction of a new syntax, it'd be an easier sell to make something a bit more complex. But since they're here to stay for many years, and we're adding yet another syntax, I'd prefer to err on the side of simplicity. Would it be possible to bank on implementations' native keyword arguments to get the most efficiency? Expanding to a macro instead of a procedure has the advantage that you *have* to know the full signature of the kw-lambda before you can call it at all, so it can always be optimized. But maybe it's not too difficult to do static analysis to a useful degree even if kw-lambdas are first-class procedures. >> How would this look in detail? I haven't thought about it very much. > > Instead of > > (define foo > (keyword-lambda (a b (c d e)) > (list a b c d e))) > > you would write something like (think of a better name than `define-177'...): > > (define-177 (foo a b (c d e)) (list a b c d e)) This syntax is completely reasonable. > This would bind `foo' to a macro that would be invoked as the > procedure `foo' from the SRFI 177 example above. To avoid infinite > recursion, `foo' may not call itself. I.e. (foo 1 2 (c 3 d 4 e 5)). This is also reasonable. But it is more complex than `keyword-call` on several fronts: * It looks like an ordinary procedure call but the sublist (c 3 d 4 e 5) is magic. In (keyword-call foo 1 2 (c 3 d 4 e 5)) it's more obvious that some unusual evaluation is going on. * You can't recurse (ideally the macro would check against recursion at compile time and show a friendly error message). * You can't pass foo around because not first class. * Portable syntax-rules implementation is not possible. While faster code is nice, having 3x faster code for a somewhat absurd benchmark does not seem worth the complexity increase to me based on our current understanding of the problem. The compile-time checking of valid kw argument names makes a stronger case for macros in my book, but even then I'd still err on the side of first-class procedures as they seem simpler. > Such a `define-177' cannot be defined through `syntax-rules' though, > because `syntax-rules' do not allow one to compare symbols via > `symbol=?', only `bound-identifier=?' and `free-identifier=?' can be > emulated. In order to make it definable in R5RS and R7RS without > `syntax-case', one would have to use, say, strings instead of symbols: > > (define-177 (foo a b ("c" "d" "e")) (list a b c d e)) IMHO this makes it quite a bit less friendly to users, since strings are almost never used as syntactic markers in Scheme/Lisp; we almost always use symbols or keywords. I started out trying an approach where the first "keyword-like" form in the argument list starts the keyword arguments. But it's not possible to do with syntax-rules, so I went with the current one where the kw-args are in a sublist. It's not as natural, but the portability is important if we want a standard. >> It matters in the case where you convert existing non-keyword procedures >> into keyword lambdas, which ought to be quite common. > > I think this is exactly what one shouldn't do when the version with > keyword arguments is less efficient than the version without. For > something like `write-json', it is quite conceivable that it is also > used in a fast path. Hmm. You may be right. I've thought there's a general consensus that people "know" which procedures are on fast paths, and hence should not be tampered with. But I may be wrong about that. > Nevertheless, there is a workaround. One could extend `define-177' > such that `foo' is not only recognized as a macro keyword but also as > identifier syntax. As identifier syntax, it would expand in a (slow) > procedural version. This would also make it possible to use recursion. > (It would work along the lines `define-integrable' is defined here: > https://cisco.github.io/ChezScheme/csug9.5/syntax.html#./syntax:h1.) How portable is that? For example: $ chibi-scheme > (define-syntax foo (syntax-rules () ((_) 123))) > (foo) 123 > foo ERROR: invalid use of syntax as value: foo >> Another case it matters is in anonymous functions. If we had a macro, it >> would have to bind the keyword procedure to a name. The current one is a >> drop-in replacement for lambda. > > Where would you want to use anonymous procedures with keyword arguments? Probably only in pathological code :) You're right.