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

optional arguments



And now for something completely different...

I believe it is time to consider adopting a simple extension to the
syntax for formal argument lists in Lambda expressions to include
optionally supplied arguments.  Here's a proposal that seems to codify
the essentials of existing practice in at least a few implementations.


MOTIVATION

It was successfully argued at Brandeis that "rest" arguments suffice
for the definition of procedures that take arbitrary numbers of actual
arguments.  Although this is certainly true, it has at least three
disadvantages.  First, a direct implementation of optional arguments
could be more efficient by avoiding the extra consing needed for a
fully general rest argument.  Second, error checking is more sporadic
when programmers supply their own mechanisms.  It is cumbersome to
specify a finite number of optional arguments given the open ended
nature of rest args.  Third, providing for optional arguments is so
common a paradigm that readability and portability would be enhanced
if we could agree on a standard mechanism.


ISSUES

I see several issues to be resolved:

(1) An extended syntax allowing optional arguments must be compatible
with the existing standard for Scheme.  It is also desirable that it
not conflict with existing extensions for optional arguments and
existing or proposed extensions in other directions.  (For example,
anyone favoring an extension of formal parameter lists to allow
destructuring of arguments has few available syntactic alternatives.)

(2) It must be decided whether a full or barebones facility should be
standardized.  If the latter, the extended syntax for optionals should
also allow for further extension.

(3) It should be possible to determine at run time whether a
particular optional argument was supplied by the caller.  One way to
do this is to supply a count of the number of arguments passed to the
called procedure.  Another way is to allow the called procedure to ask
whether a given argument were supplied.

(4) It is useful to allow specification of values to be bound to
optional arguments when actual parameters are not supplied by the
caller.  This facility is convenient but can be built upon the ability
to determine whether a value had been supplied by the caller.  It also
has subtle problems, such as the exact lexical environment to be used
in evaluating the initializing expression.


INFORMAL DESCRIPTION

I suggest that we adopt the following mechanism for optional arguments
as a non-essential feature.  It is basically the approach used
internally here at TI based on our understanding of a similar facility
in MIT Scheme.  However, it includes modifications suggested by Will
Clinger.

-- Formal argument lists containing optional arguments are
distinguished by the presence of the keyword #!OPTIONAL, which acts
like &OPTIONAL in Common Lisp.  (I could write out an informal
description here based on Steele's book, but there's no need to yet.)

-- Formal variables which do not receive values are said to be "bound"
but not "assigned".  It is an error to reference an unassigned
variable.  Ideally, an implementation would trap on such references.
However, it may choose to mark unassigned variables with a
distinguished value (or values), such as the token #!UNASSIGNED.

(The R^3RS formal semantics assumes that all variables are bound
in the initial environment, but this is an area where implementations
differ.  Some implementations may extend the syntax for DEFINE so
(DEFINE X) creates a binding for X but leaves it unassigned.)

The special form (ASSIGNED? <id>) returns #T iff the variable named by
<id> has been assigned a value (e.g., from the calling procedure's
actual argument list).  The value of (ASSIGNED? X) is unspecified if X
is unbound.  For example the value of (LETREC ((X (ASSIGNED? X))) X)
is not clear, just as the value of (LETREC ((X X)) X) is unspecified.

-- A "rest" argument is always assigned.  If the list of actual
arguments is no longer than the number of required and optional
formals, the rest argument receives the value ().

-- Optional arguments may be given default values by first testing
them with ASSIGNED? as in the following example:

	(lambda (a #!optional b)
	  (let ((b (if (assigned? b) b (+ a 42))))
	    ...))
or
	(lambda (a #!optional b)
	  (when (not (assigned? b)) (set! b (+ a 42)))
	  ...)

This makes clear the environment that the initialization expression
for B is to be evaluated in.


FORMAL SEMANTICS

Will Clinger has kindly volunteered the following formal semantics for
ASSIGNED? and LAMBDA.  The "\" character should be read as Greek lambda.

   E [[ (assigned? I) ]]
       = \ r k . hold (lookup r I)
                      (single (\ e . e = undefined --> false, true))

   E [[ (lambda (I0* #!optional I1*) C* E0 ]]
       = \ r k . \ s .
           new s \in L -->
            send (< new s | L,
                  \ e* k' .
                    (# e* >= # I0*) & (# e* <= (# I0* + # I1*)) -->
                       tievals
                         (\ a* . (\ r' . C [[ C* ]] r' (E [[ E0 ]] r' k'))
                                 (extends r (I0* concat I1*) a*))
                         (e* concat (seq (# I0* + # I1* - # e*) undefined)),
                       wrong "wrong number of arguments">
                  in E)
                 k
                 (update (new s | L) unspecified s),
            wrong "out of memory" s

   E [[ (lambda (I0* #!optional I1* . I) C* E0 ]]
       = \ r k . \ s .
           new s \in L -->
            send (< new s | L,
                  \ e* k' .
                    # e* >= # I0* -->
                       tievalsrest
                         (\ a* . (\ r' . C [[ C* ]] r' (E [[ E0 ]] r' k'))
                                 (extends r (I0* concat I1* concat <I>) a*))
                         ((takefirst e* (# I0*))
                          concat (seq (# I0* + # I1* - # e*) undefined)
                          concat (dropfirst e* (# I0*)))
                         (# I0* + # I1*),
                       wrong "too few arguments">
                  in E)
                 k
                 (update (new s | L) unspecified s),
            wrong "out of memory" s

where

   seq = \ n x . n <= 0 --> <>, <x> concat (seq (n - 1) x)


DISCUSSION

I don't propose a syntactic extension for specifying default
initialization more conveniently because (1) the obvious syntax
would conflict with an extension for argument list destructuring;
(2) testing whether a variable had been supplied by the caller or from
evaluation of the default initialization expression becomes more
complicated; (3) the explicit mechanism given here seems quite
readable and convenient to me; and (4) I think it's important to make
explicit which environment the initialization form is evaluated in and
to allow variations.

I've mentioned destructuring several times because I have the
impression much of the opposition to optional arguments comes from the
desire to leave the way open for destructuring lambda lists.  It seems
to me that the simple addition of the #!OPTIONAL keyword should not
seriously hinder destructuring, since it is unambiguous and can be
easily recognized.

I welcome all comments.

Regards,
David Bartley