ashinn / chibi-scheme

Official chibi-scheme repository
Other
1.21k stars 141 forks source link

`let-keywords` positionality, rest problems #866

Closed dpk closed 1 year ago

dpk commented 1 year ago

The intention seems to be that let-keywords allows keywords to be interspersed anywhere in an argument list, and anything else goes to the rest argument.

In practice it doesn’t work this way because keywords have to be in an even-numbered position (assuming counting starts from 0) and the rest argument will silently drop a final argument entirely if it isn’t in an odd-numbered position:

> (let-keywords '(2 a: 3) ((a a: 1) rest) (list a rest))
(1 (2 a:))
> (let-keywords '(a: 2 3) ((a a: 1) rest) (list a rest))
(2 ())
> (let-keywords '(3 5 a: 2 4) ((a a: 1) rest) (list a rest))
(2 (3 5))
> (let-keywords '(3 5 a: 2 4 6) ((a a: 1) rest) (list a rest))
(2 (3 5 4 6))

I think it is altogether a mistake for let-keywords to allow keywords to come in the middle of other arguments, because the usual pattern in Chibi is to define a procedure such as (lambda (arg1 arg2 . o) (let-keywords o (...) ...))). In this case the things between the keyword arguments would just be ignored. This is asking for confusion if someone accidentally puts an expression in the wrong place in a keyword arguments list.

Probably let-keywords should first split the input list into keywords and non-keywords, taking keywords only from the head of the list and non-keywords being the unmodified tail from the original, as rest arguments usually are expected to be. Keywords should still be required to be in even-numbered positions, but they have to start at zero and not be interspersed with other arguments. (Bonus points if an error is signalled when the non-keywords part is non-null and there was no rest identifier given to the let-keywords expression.)

ashinn commented 1 year ago

Hmmm, it looks like I could be more clear in the documentation here.

You can consider let-keywords[*] similar in spirit to SRFI 89/DSSSL/CL, except that there are no optional arguments - everything is either required or named. It was neither intended nor implemented to allow keywords in the middle of "other arguments."

After the last required argument, everything is a keyword argument. It is fairly common to want to chain procedures which accept keyword arguments, so we allow a rest parameter to collect all keywords not explicitly included in the formal parameters. Thus you bind what you want and pass on the rest. This is also similar to Python's **args if you're familiar with that.

Because the whole point of keyword arguments is to be position-independent, you can of course intermix known and unknown keywords in any order, which you are misinterpreting to be "optional" arguments.

ashinn commented 1 year ago

I've improved the documentation. I also reviewed all of your examples and verified they are WAI, so added them as tests.