[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

A Hole in Common Lisp

Consider the nature of arguments to functions. In Common Lisp we have
these two notions: positional arguments and named arguments. In the first
case values are associated with variables depending on the positions of
passed arguments.  In the latter case, the binding happens depending on
the names of the arguments, which are passed along with the arguments.

For example:

(defun f (x y &key a b) ...)

(f 1 2 :b 3 :a 4)

X and Y are positional, and A and B are named.

There is a second pair of notions: required arguments and optional arguments.

Does Common Lisp have all of the combinations of these notions?

         | positional |	named |
required |    yes     |  no   |
optional |    yes     |  yes  |

It doesn't seem so. Keyword arguments combine named and optional.

Also there is no way to have a function with both required keyword and
truly optional positional arguments.

Suppose someone writes

(defun f (x y &optional a b &key foo bar baz ola)
 (list x y a b foo bar baz ola))

and this form is evaluated:

(f <a1> <a2> <a3> <a4> <a5> <a6> <a7> <a8>)

The binding of the variables in the function definition goes like this:

x is bound to <a1>, y is bound to <a2>, a is bound to <a3>, and b is
bound to <a4>. Now we lift our heads from the sand and look at what
<a5> is. It should be one of :foo, :bar, :baz, or :ola. Now suppose
we had written:

(f 1 2 :foo 4 :bar 5 :baz 6)

Did the user intend for a to be bound to :foo, or did he intend for
a and b to not be supplied?

Here is an off the wall proposal for lambda expressions; it includes
Moon's proposal for non-keyword package symbols to be names of arguments:

(lambda ({var}*
         [&optional {var | (var [initform [svar]])}*]
         [&required-key {var | (name var)}*]
	 [&rest var]
         [&key {var | (name var)} [initform [svar]])}*
         [&aux {var | (var [initform])}*])
   {declaration | documentation-string}*

where var, initform, svar are exactly as before, and name is the name of
a keyword argument, which can be a symbol in any package. Parsing an
argument list proceeds as follows:

Let nrp be the number of required positional parameters. The first nrp
supplied arguments are paired with the nrp required positional parameters.
The remaining supplied arguments are scanned left-to-right, starting with
scanning for positional optionals.  If a supplied argument is EQ to one of
the required named parameters names, the scanning for positional optionals
ends and all unpaired positional optionals are defaulted according to the
lambda-list specification; then named parameter parsing begins. If a
supplied argument is not EQ to any required named parameter name, it is
paired with the next positional optional parameter.

Once the scanning for optional positional parameters has ended, scanning
for named parameters begins.  Named parameter parsing is as in current
Common Lisp, except that if, at the end, there are unsupplied
required named arguments, an error is signaled.

The difference between this parsing algorithm and the current Common Lisp
one is that the occurrence of the first required named parameter stops
parsing of positional optionals. Therefore, the only way to pass the name
of required named parameter is as a required positional or required named
argument. Also, if optional named arguments appear to the left of all
required named arguments, they can be taken as positional optionals.


(defun f (x y &optional a b &required-key foo bar &key baz ola)
 (list x y a b foo bar baz ola))

(f 1 2 3 4 :foo 5 :bar 6 :baz 7 :ola 8) 
 => (1 2 3 4 5 6 7 8)

(f 1 2 3 :bar 4 :baz 5 :foo 6 :ola 7)
 => (1 2 3 nil 6 4 5 7)

(f 1 2 :baz 3 :foo 4 :bar 5)
 => (1 2 :baz 3 4 5 nil nil)

(f 1 2 :baz :foo 3 :bar 4)
 => (1 2 :baz nil 3 4 nil nil)

This has the advantage that it could make some things in CLOS easier
to deal with. For example, we could define methods that discriminated
on the names of required named arguments:

(defmethod foo (x y &required-key foo bar) `(foobar ,x ,y ,foo ,bar))
(defmethod foo (x y &required-key baz ola) `(bazola ,x ,y ,foo ,bar))

(foo 1 2 :foo 3 :bar 4) => (foobar 1 2 3 4)
(foo 1 2 :baz 3 :ola 4) => (bazola 1 2 3 4)
(foo 1 2 :foo 3 :ola 4) => error