-----
Although if you had something like this:
(defun fn (a &o (b 'b) (c 'c) &k (d 'd))
with a usage like this:
(fn 1 'd 'e)
... how would you know whether:
1) 'd is the value of the first opt arg and 'e is the value of the second (the key arg unsupplied)
or
2) 'd is the key for the key arg, and 'e is its supplied value (the 2 opt args unsupplied)
?
Maybe I'm missing something here, but it seems to me that unless you have special syntax for keywords, you will get into trouble.
And if you have to introduce special syntax for keys anyway, it is just as well to make every single argument keyable on its symbol (even vanilla ones), and just worry about combining &o and &rest (which should then be doable).
-----
The interpretation is that d and e are the two optional args, so any caller wanting to supply a keyword arg has to supply the optionals. Recall that I said it was possible, not sensible. :) But in tool design I think we should let users hang themselves rather than guess (perhaps wrongly) that no one would ever come up with a good use for such a thing.
A second, lesser motivation is that CL works that way.
-----
(def read-from-string (s &o eof-error-p eof-value
&k start end preserve-whitespace)
...)
Which does make me think the guess about keywords being added as an afterthought might be spot on.-----
-----
[1]> (defun test (a &optional (b 'b) (c 'c) &key (d 'd))
(list a b c d))
TEST
[2]> (test 1 :d :e)
(1 :D :E D)
Optional parameters always bind first in CL, and I believe dsb is written to mimic that behavior.Having all parameters be keyword arguments as well might be interesting, but it wouldn't avoid optional/keyword confusion.
-----
Why not?
Let's say you have a function with 3 standard args followed by 2 opt args. So you have 5 args, all keyable on the symbol you give them in the def.
Let's further say that in a call to this function, I key the middle standard arg (#2 in the def) plus the first opt arg (#4 in the def) and also, I omit the second opt arg (#5 in the def). So, I'm supplying two standard args apart from the two args I'm keying. Then the function call parser would know, after identifying the 2 keyed args and matching them to positions #2 and #4 in the def, that the first non-key arg supplied corresponds to position #1 in the def, the second non-key arg supplied corresponds to position #3 in the def, and that an arg for position #5 in the def is missing, leading to the usage of the default value for this opt arg.
This would even work when you want to raise an error for a non-supplied, non-opt arg.
Wouldn't this work quite intuitively (keying an arg when calling a function "lifts it out" of the normal "vanillas then optionals" argument sequence, shortening that sequence, put keeping a well-defined order for it)? (You would need special syntax for keys in this proposal. My suggestion is a colon appended to the arg symbol, rather than prepended, like in CL.)
Can someone give a counterexample if they think this somehow wouldn't work?
&rest args are left as an exercise for the reader :-)
-----
What I would like to see is optional, keyword, and rest arguments. Imagine something like this:
(def foo (k v)
'type (k int)
(+ k v))-----
(dsb (r1 &o o1 &r rest &k k1) data ....)
If data was (1 2 'k1 3) then most params would be bound as expected and then rest would be bound to (k1 3).But now the data (1 2 'k1 3 4) causes an error "Key list is not even", ie, once you say &k you undertake certain obligations as the caller. Even if you even up the list:
(1 2 'k1 2 3 5)
...you get an error "3 is an invalid keyword", because 3 appears in a keyword position. This can be avoided by announcing your intention to have undeclared keywords: (a b &rest rest &key k1 &allow-other-keys)
That of course is CL, and it kinda makes my day that if I were crazy enough to extend dsb in Arc I would end up with: (a b &r rest &k k1 &aok)-----
Was there ever any doubt? :)
Still requiring extensions from earlier posts:
(def dsb-params-parse (params)
(withs (reqs nil key? nil opt? nil keys nil opts nil
rest (mem '&r params)
rest-var (cadr rest)
aok? (find '&aok cddr.rest)
resting? nil
no-mas nil)
(each p params
(if no-mas (assert nil "No params &aok, OK?" ',params)
(is p '&o) (do (assert ~opt? "Duplicate &o:" ',params)
(assert ~key? "&k cannot precede &o:" ',params)
(= opt? t))
(is p '&k) (do (assert ~key? "Duplicate &k:" ',params)
(= key? t))
(is p '&r) (= resting? t)
(is p '&aok) (= no-mas t)
key? (push-end p keys)
(and opt? (no resting?)) (push-end p opts)
(no resting?) (do (assert (~acons p) "Reqd parameters need not be defaulted:" p)
(push-end p reqs))))
(prt 're-obj!!!!! reqs opts rest-var keys aok?)
(obj reqs reqs opts opts rst rest-var keys keys aok? aok?)))
And man was I happy to have the above as a breakout from the macro itself: (mac dsb (params data . body)
(w/uniq (tree kvs valid-keys aok?)
`(withs (,tree ,data
,@(let plist (dsb-params-parse params)
(prn `(reqs ,plist!reqs))
(prn `(rst ,plist!rst))
(prn `(keys ,plist!keys))
(prn `(&aok ,plist!aok?))
(with (n -1)
(+ (mappend [list _ `(nth ,(++ n) ,tree)] plist!reqs)
(mappend [list (carif _) `(if (< ,(++ n) (len ,tree))
(nth ,n ,tree)
,(cadrif _))] plist!opts)
`(,plist!rst (nthcdr ,(++ n) ,tree))
`(,valid-keys ',plist!keys)
`(,aok? ',plist!aok?)
`(,kvs (do (prt 'foing ,valid-keys)
(when (and ,plist!rst ,valid-keys)
(assert (even (len ,plist!rst)) "Keyword list not even" ,plist!rst)
(let ,kvs (pair ,plist!rst)
(prt 'vetting ,valid-keys 'againt ,kvs)
(unless ,aok?
(assert (all [find (car _) ,valid-keys] ,kvs)
"Invalid key in" (map car ,kvs)))
(prt 'kvs!!!!! ,kvs)
,kvs))))
(mappend [list (carif _)
`(do (prt 'kvs!!! ',(carif _) ,kvs)
(aif (assoc ',(carif _) ,kvs)
cadr.it
,(cadrif _)))] plist!keys)))))
,@body)))
What is missing (largely) is graceful handling of invalid use.-----
Lock and load! Add the keywords!! :)
-----