Email list hosting service & mailing list manager

Procedures with keyword arguments should be macros not procedures Marc Nieper-Wißkirchen (17 Oct 2019 09:17 UTC)
Re: Procedures with keyword arguments should be macros not procedures Lassi Kortela (17 Oct 2019 10:16 UTC)
Re: Procedures with keyword arguments should be macros not procedures Marc Nieper-Wißkirchen (17 Oct 2019 10:48 UTC)
Intricate lambda list syntax Lassi Kortela (18 Oct 2019 08:26 UTC)
Re: Intricate lambda list syntax Shiro Kawai (18 Oct 2019 09:18 UTC)
Re: Intricate lambda list syntax Lassi Kortela (18 Oct 2019 14:29 UTC)
Re: Intricate lambda list syntax Shiro Kawai (18 Oct 2019 17:32 UTC)

Re: Procedures with keyword arguments should be macros not procedures Lassi Kortela 17 Oct 2019 10:16 UTC

> SRFI 177 gives a number of good reasons why one wants to add/invent
> keyword arguments to procedures. One reason is those keyword arguments
> allow one to introduce new parameters without breaking old code.
>
> However, this shouldn't come with the price of decreased efficiency.
>
> In the portable R5RS/R7RS sample implementation coming with SRFI 177,
> whenever a procedure defined with `keyword-lambda' is being called, a
> lot of work for the keyword dispatch is done, which causes procedures
> defined through `keyword-lambda' to be noticeably less efficient than
> ordinary procedures defined through `lambda'.

Yes, it traverses a whole plist of keyword arguments on every procedure
call. I originally had them in a vector, but a vector doesn't work
portably as a rest argument so I changed it to a list.

Are they noticeably less efficient though? I imagine you'd have to do
quite a few iterations to have a perceptible speed difference. I would
not use keyword arguments at all in the fast path; I don't think they
are the right tool for every job (Common Lisp remarks below).

Apart from efficiency concers, classic Common Lisp-style keyword args
are also hard to type-check statically. IIRC that's why Kawa and Racket
have new keyword semantics where a keyword is just a syntactic marker in
a procedure call, and not a self-evaluating object.

> The same will most likely be true for native implementations when the
> call site does not know the called procedure as it will most likely
> have to resort to runtime dispatching as well.

Doubtless true, though the argument representation can be more efficient
than a list.

> Therefore, I think it is much better if "procedures" taking keyword
> arguments are implemented as macros instead, the runtime dispatch
> overhead into a compile-time dispatch overhead, which is fine.

How would this look in detail? I haven't thought about it very much.

> Of course, "procedures" taking keyword arguments wouldn't be
> first-class procedures anymore, but I don't see a convincing use case
> where this would matter.

It matters in the case where you convert existing non-keyword procedures
into keyword lambdas, which ought to be quite common. So you start out
with some procedure like "write-json" and after a few months you realize
that options are needed for indentation, newlines, character encoding
and the like. You can then replace

(define (write-json obj port) ...)

with

(define write-json
   (keyword-lambda (obj port (indent newlines encoding))
      ...))

The new procedure with keywords can be used the same way as the old one,
anywhere the old one was used. You don't need to grep the codebase in
case somebody used it first-class and change those instances.

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.

In designing this SRFI my first priority was to have a simple interface
like this.

> We can even get rid of `keyword-call', whose current need is a bit unfortunate.
It's a bit verbose, but as you say it's required for portable code if
procedures are first class. And it makes it clear that keyword magic is
going on; if keyword lambdas are macros, it's not clear what
obvious-looking portable syntax we should use to pass the keywords. We
need to use symbols in some way since we can't rely on portable read
syntax for keywords.

> P.S.: In case someone finds them valuable, a number of arguments in
> favor and against keyword arguments are raised here:
> https://github.com/cisco/ChezScheme/issues/108.

Hehe, I was just about to link to that thread in response :D

The efficiency point is valid. However, Common Lisp has had keyword
arguments for ages and its users rarely complain that they are slow. The
usual practice is not to have keyword arguments in critical code paths.
They are best at the public interface of an API that has lots of
options. A HTTP client is the perfect example: no matter how many
options you parse, the lion's share of runtime is still taken up by
network I/O. To me it's like arguing that CLOS classes are slow: they
are not the right tool for every job.

One lesson from CL is that you do have to do extra work in the compiler
is fast library functions take keyword arguments. Some of the CL
string/sequence functions do, and SICL has to compensate (search for
"define-compiler-macro" in
<https://raw.githubusercontent.com/robert-strandh/SICL/2cda407575b39b37ec83de988c13c996cba192ba/Code/Sequence/sequences.lisp>).
Then again those functions are so generic that a fast compiler might
need to cover those special cases even without keyword arguments,
leading to about the same amount of code.