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

More words on the scoping of declarations



Having been gone for three weeks, I was unable to properly defend my
proposal on the scoping of declarations.  Although I've been back for
three days now, I have only now finished reading the incredible torrent
of mail spewed out over that time.  I therefore ask for your indulgence
as I refer to messages now more than 25 days old.  I have included
contextual excerpts for those of you who haven't just read it all at
once.

	Date: 19 Jul 86 17:17 EDT
	From: NGALL@G.BBN.COM
	
	    Date: 11 Jul 86 18:43 PDT
	    From: Pavel.pa@Xerox.COM
	    
	    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.
	
	I don't think you mean 'any' here; I think you mean 'any declaration
	that concerns the bindings of variables' (cf. pg. 154).  You cannot
	mean 'any' since in the example below
	(let ((x 1))
	  (declare (inline foo))
	  (foo x))
	the declaration does not 'coincide with a particular lexical
	identifier scope.' A more extreme example is
	(let (...)
	 (declare (optimize ...))
	 ...)
	in which the declaration does not refer to ANY named entity.

Being a disciple of the Mad Hatter, I said what I meant and meant what I
said.  A declaration need not refer to a named entity in order to share
that entity's scope.  In the second example you gave, the OPTIMIZE
declaration applies to the bindings made in the LET and to all code in
the body of the LET.  Thus, if we were asking to optimize speed at the
expense of space, the compiler would be encouraged to bind the variables
in the fastest way possible, even if it used up an extra stack cell, for
example.  I fully understand that most implementations, including that
of Xerox, haven't the freedom to further optimize the creation of
bindings (though it is a fascinating concept...).  The point is that
those bindings \are/ covered by the declaration and that the init-forms
for the bindings are \not/ so covered, as stated in my proposal.

	Unfortunately, TYPE and IGNORE are the only decl specs that only
	concern bindings.  SPECIAL concerns bindings also, but it
	also pervasively affects references (this is what you want changed).

I am \not/ trying to change the so-called ``pervasive'' nature of
special declarations, only their scope.  More on this later.

	And FTYPE and INLINE simply confuse me.

How can FTYPE be confusing when TYPE is not?  They perform precisely
symmetrically along the value/function axis.  TYPE makes a statement
about the kinds of \values/ that can be referred to by the \value/-names
given, while FTPYPE makes a statement about the kinds of \functions/
that can be referred to by the \function/-names given.  No difference,
no confusion.

	First of all, FTYPE is not explicitly stated to be a pervasive decl
	spec, I believe this is an omission.  Secondly, there is this
	confusing sentence at the end of their descriptions:

	"Note that the rules of lexical scoping are observed; if one of the
	functions mentioned has a lexically apparent local definition (as made
	by FLET or LABELS), then the declaration applies to that local
	definition and not to the global definition."

	This makes it sound like FTYPE and INLINE are like SPECIAL: they
	concern bindings AND affect references (but not pervasively?).

	For example,
	(FLET ((zap (...)...(zap...)...))
	  (declare (inline zap))
	  ...)

	Is the call to zap in the local definition of zap affected by the
	declaration?  My reading of CLtL and commonsense make me answer no.
	The call to zap is not a call to the local def. of zap, and according
	to my reading of the sentence quoted above, the decl. only affects
	references within the scope of the innermost binding of the name zap.
	But this interpretation depends upon not interpreting 'pervasively' as
	stated in CLtL. Is my interpretation correct?

One is strongly reminded of rabbinical study of the Talmud...
This whole view of pervasive vs. non-pervasive is a red herring based
upon some unclearly-written prose in CLtL.  A given declaration has a
certain scope and has an effect (possibly empty) on every single
language construct in that scope.  Thus, \every/ declaration is
``pervasive'' in the sense of CLtL.  To describe the meaning of a given
kind of declaration, it is necessary and sufficient to lay out its
effect on every kind of language feature that can appear within its
scope; clearly, most declaration-kinds will have non-empty effects on
only a small set of language features.  For example, a SPECIAL
declaration affects variable bindings and references, but not
function-calls or other declarations.  The DECLARATION declaration
affects only other declarations and none of variable bindings,
references or function calls.  I will lay out my understanding of the
meanings of all of the CLtL declaration-kinds in another message (since
this one is going to be too long as it is).

	I propose the following change to CLtL (which pretty much agrees with
	both CLtL and Pavel):
		
		...

I disagree with the statement that Nick's proposal agrees with mine.


		...

	Some examples should help clarify this:

	(let ((y x))
	  (declare (type list x))
	  ...x...)
	Both references to x are affected.

	(let ((x x))
	  (declare (type list x))
	  ...x...)
	Only the second reference to x is affected (since the first reference
	is not within the scope of the binding named by x).

This is losing.  Either the init-forms of the LET should be covered by
the declarations or they should not, but it shouldn't depend upon what
variables are being bound.  It is precisely behaviour like this against
which I am raging: the scope of declarations corresponding to random,
arbitrary, and hard-to-remember rules as opposed to the same, sane rules
of lexical scoping around which the whole remainder of the language
revolves.

	(FLET ((zap (...)...(zap...)...(zoop...)...))
	  (declare (function zap (...)...)
	           (function zoop (...)...))
	  ...)
	The call to zap is not affected, but the call to zoop is.

This is the example that really hurts.  The world will be a simpler
place if we can simply agree that every declaration has a certain scope,
regardless of what constructs appear in that scope.  Thus, the scope of
these declarations either includes or does not include the body of the
function ZAP.  If they do (and I think that they should not), then both
function calls shown should be affected.  If the scope does not include
that body, then neither call is affected.  Clear?

Note that I specified a \single/ scope for both declarations above, not
a separate one for each; the only exception to this policy should be the
sequential binding forms, including LAMBDA.

	I believe this proposal is complete, consistent, and simple to apply.

I believe this proposal is incomplete (since it does not include a
specific enumeration of the semantics of all declarations in all of the
special forms), inconsistent (see my last set of comments above) and
difficult to apply (it requires examination of the names of variables
being bound, as opposed to having scope and semantics independent of
specific context).

-----------

The next proposal was from Bawden and was later dubbed ``Bawden's
Alternate Proposal'':

	Date: Mon, 21 Jul 86 16:02:37 EDT
	From: Alan Bawden <ALAN@AI.AI.MIT.EDU>
	
	...
	
	Actually, the more I think about this as a simplification, the more I
like
	it.  So here is a counter-proposal for rationalizing the semantics of
	declarations:


	1.  All declarations are completely pervasive.  That is, if you write

	  (locally (declare (special foo)) ...)

	then every occurance of FOO within the body is taken to be a special
	reference or a special binding.  The only way to escape from the
effects of
	a declaration within the body, is to explicitly shadow the declaration
with
	another.  This applies to -all- declarations: FTYPE, INLINE, etc.

	2.  Declarations that occur in the bodies of special forms (DEFUN, LET,
DO,
	etc.), and in LAMBDA expressions, are taken to mean the same thing as
if
	the entire form was enclosed in a LOCALLY containing the same
declarations.
	So

	  (let ,pairs ,@dcls ,@body)

	and

	  (locally ,@dcls (let ,pairs ,@body))

	are completely equivalent.  

	(Since LAMBDA expressions aren't forms, the equivalent using LOCALLY
isn't
	always completely straightforward to construct.  For example, this
case:

	  ((lambda ,vars ,@dcls ,@body) ,@vals)

	is equivalent to using LOCALLY as follows:

	  (funcall (locally ,@dcls (function (lambda ,vars ,@body))) ,@vals)

	.)

One certainly can't fault this proposal on the ground of complexity.
No, my major objection to Alan's plan is that it sets up an entirely
separate scoping mechanism for declarations.  That mechanism is entirely
lexical and straightforward and all that, but it's not the same one that
the binding of names uses.  Why have two scopes when one will do?  Also,
I agree with Paul the Greek that the following is undesirable:

	Date: Mon, 21 Jul 86 17:55:05 EDT
	From: Alan Bawden <ALAN@AI.AI.MIT.EDU>

	    From: "BACH::GREEK" <greek%bach.decnet@hudson.dec.com>
	    Bawden's proposal would result in the following.

	      (FLET ((FOO (X) (FOO X X)))
	        (DECLARE (FUNCTION FOO (INTEGER) INTEGER))

	        ... (FOO 5) ...)

	    The declaration for FOO would pertain to both the locally-defined
	    FOO and the outer FOO used in its body.

	This is correct.  Since function names are lexically scoped, you can
call
	the inner function something other than FOO with only a minor change to
	your program.  Is this situation actually common in anyone's code?

It bothers me that a single declaration can refer to two different
variables that happen to share the same name.  Notice that it is the
creation of a separate-but-equal scoping mechanism for declarations that
causes this problem; were declarations to use the same scoping mechanism
as names, this problem could not arise.

JonL wrote in support of Alan's proposal:

	Date: Wed, 23 Jul 86 03:53:30 PDT
	From: edsel!bhopal!jonl@navajo.stanford.edu (Jon L White)

	...

	It seems to me that Alan's proposal infuses declarations with the same 
	kind of scoping semantics that exists for variable bindings.  In the
form
	    (let ((a <something>))
	      . . .
	      (let ((a <something-else>))
	        . . . 
	      ))
	the meaning of this is "a is bound to something, unless it is bound
	to something else"; which is parallel to Alan's notion for declarations
	that has (unfortunately) been called "shadowing".

	I find the unification of these two scoping rules to be very
attractive.

	-- JonL --

Alan's proposal does not unify the two scoping rules, but rather grants
them both first-class status, separate but equal, as I said above.  I
believe that my proposal, linking the scope of declarations directly to
the scope of names, truly unifies the two.

I've talked enough for this letter.  I will send out a separate message
revising and completing my proposal in as much detail I would expect
(hope) the language specification to contain.

	Pavel