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

Re: DECLARE SPECIAL Considered Confusing

In this message I will give a complete description of my proposal for
declaration scoping.  I won't attempt to argue for it on the basis of
``obviousness'' or clarity, though, because I think that there are
people who find each of the two ways of scoping special declarations
confusing.  Rather, I like this way of doing things because it satisfies
an important (to me) consistency criterion: the scope of \any/
declaration coincides with a particular lexical identifier scope.

There are six primitive forms in Common Lisp that can both bind
identifiers and carry declarations:
I will describe the scoping rules for declarations in each of these

The scope of the declarations within a LET is precisely the scope of the
variables being bound.  That is, any declarations within a LET affect
all of the bindings themselves and all forms appearing within the scope
of those bindings.  Because the various initial-value expressions for
the bindings are not evaluated within the scope of those bindings,
neither are the declarations in effect over those expressions.  Thus, in
the form below,

	(let ((<var-1> <expr-1>)
	      (<var-n> <expr-n>))

the given <declarations> affect the bindings of <var-1> through <var-n>
and the <body-forms>, but not <expr-1> through <expr-n>.

The LET* special form is most easily understood as a series of nested
LET's.  The question is, how are the declarations to be distributed
among those various nested scopes?  The answer is to leave almost all of
the declarations in the innermost LET and to distribute to each of the
other scopes only those declarations that could apply to the variable
being bound there.  Thus, the following LET*:

	(let* ((a <init-a>)
	       (b <init-b>)
	       (c <init-c>))
	   (declare (special a b c)
	            (inline foo)
	            (optimize (speed 3))
	            (type (integer 0 *) b))

is equivalent to this set of nested LET's:

	(let ((a <init-a>))
	   (declare (special a))
	   (let ((b <init-b>))
	      (declare (special b)
	               (type (integer 0 *) b))
	      (let ((c <init-c>))
	         (declare (special c)
	                  (inline foo)
	                  (optimize (speed 3))

This is simply a syntactically pleasing way to lexically bind functions
to identifiers appearing in functional position.  Thus, we should think
of the following FLET:

	(flet ((<var-1> (<args-1>) <body-1>)
	       (<var-n> (<args-n>) <body-n>))

as this LET:

	(let ((<var-1> #'(lambda (<args-1>) <body-1>))
	      (<var-n> #'(lambda (<args-n>) <body-n>)))

except that the <var-i> are bound as function identifiers instead of as
variables.  The scope of the declarations thus includes the <body-forms>
but none of the <args-i> or <body-i>.

This is a version of FLET that differs only in the scope of the bindings
of the names for the functions.  Thus, the scope of the declarations
should be the same as in FLET except for those applicable to the
bindings themselves, which should cover the entire construct.

The scoping here is precisely as in FLET, covering the bindings of the
macros (whatever that might mean) and the body of the form, but not the
bodies of the macros themselves.

The scope of the first required parameter in a LAMBDA covers all other
bindings and code within the LAMBDA form.  The declarations have the
very same scope, covering all executable code within the form.  (In the
case where no required parameters exist, the declarations still cover
the whole form, the scope of a new, imaginary, required parameter.


I believe that this proposal is consistent and relatively easy to
reconstruct from the simple principle that the scope of a declaration is
always the lexical scope of some identifier.