[Date Prev] [Date Next] [Thread Prev] [Thread Next] Date Index Thread Index Search archive:
Date:Mon Jan 18 15:13:00 1993 
Subject:Re: lvars and standards (was: Re: List and Vector Syntax) 
From:Brian Harvey 
Volume-ID:930118.02 

sfk@otter.hpl.hp.com (Steve Knight) writes:
>The first point related to the ease of debugging:
>
>This is a nice point, although more about the programming environment
>than about lexical or dynamic binding.  To see that this is true, it should
>be plain enough that inside an interrupt-context that there is a current
>environment -- exactly the same as with dynamic binding.  And to access 
>bindings deeper than the current binding requires the ability to redirect
>context.

The point is that with dynamic scope you automatically have access to
the "deeper" bindings, unless they're shadowed.  There is no need for
any special mechanism to allow explicit moving around between environments.

>The second point seems incorrect, as I understand it:
>
>I don't see that this discusses the difference between lexical and 
>dynamic binding.  It shows nothing more than the difference in syntax
>for describing a nullary-procedure (a "block" in SmallTalk terminology).
>In LOGO and SmallTalk, the [ ... ] syntax is used for denoting procedures
>in a concise fashion.  By contrast, Scheme has no concise representation
>for nullary-procedures -- and Pop is even more verbose than Scheme!

No, I don't think this is simply a matter of notation.  The square
bracket syntax in Logo doesn't represent a nullary procedure; it just
represents a literal list.  When you make a list, Logo doesn't create
a closure or anything.  And a list can be constructed from pieces,
not only typed directly in brackets.  For example, instead of saying

	RUN [PRINT :X + 3]

you could say something like

	RUN SENTENCE [PRINT :X +] :NUMBER

(This is a silly example but such situations do come up occasionally.)
The stuff in the brackets is just plain text.

My point was that with dynamic scope, this just plain text can be
evaluated in a subprocedure, without having to do anything special
to carry a closure around, such as creating a procedure.  The issue
isn't merely implementation efficiency, although I'd hate to have to
generate a closure every time a user constructs some piece of text,
just in case that text later gets executed!  The issue is that the
user doesn't have to know what a closure or an environment is.  I claim
that the natural thing, for a kid, is that every variable that exists
right now is accessible right now, and that's what dynamic scope provides.

>The choice of default binding in programming languages for children,
>such as LOGO, has to be a poor choice.  It is extremely confusing
>for children (and adult novices) to be faced with global/local
>name shadowing.

It's only confusing if you use the same name for different purposes,
a practice that should be avoided no matter what scoping rules you have.
The human reader will be confused even if the computer isn't.  The
natural way to program is to give every variable a unique name, for
ease of documentation, except maybe for the really locally used ones
like "list" inside a list utility function, which you don't expect to
be able to use elsewhere.  I think the fact that you get screwed if you
use the same name for different purposes is natural, whereas the fact
that you *can't* use a perfectly unambiguous name because you happen to
be in a different procedure is confusing.

The proof of the pudding is that lexically scoped languages always end up
either nesting procedures inside each other, which I find completely
unreadable (Pascal and Scheme), or else forcing the programmer to resort
to global variables half the time because it's just too inconvenient
otherwise (C).

>The programmer wanted to redefine the printing function locally.

This is not the sort of thing a beginner would do.  In Logo, in order
to redefine a primitive you have to use a marked-as-dangerous command
(the convention is that things whose names start with a period aren't
for beginners).  But also, I'd say it's a bug in the language
implementation if something that's advertised as a primitive actually
depends on some other primitive; the former should refer to the latter
through some non-user-visible alternate name or something.


By the way, here's my favorite serious example of a program that's
easier to write with dynamic scope: a recursive descent Pascal compiler.
[See my _Computer Science Logo Style, vol. 3: Advanced Topics_ -- advt.]
You have some data structure that contains the variable names and
corresponding memory locations available at the point in the program
you're compiling right now.  And you have a procedure within the compiler
to handle each syntactic form of Pascal.  So, in particular, there are
compiler procedures COMPILE.PROCEDURE and COMPILE.FUNCTION, each of
which establishes a new symbol table.  The procedures that compile
other kinds of statements, such as COMPILE.ASSIGNMENT and COMPILE.IF
and so on, have to refer to the current symbol table.  They can't
be lexically within the procedure that establishes the symbol table,
because that might be either COMPILE.PROCEDURE or COMPILE.FUNCTION and
you don't want to have to have two copies of COMPILE.IF, one inside
each (and a third inside COMPILE.PROGRAM).  With dynamic scope, each
new symbol table is correctly inherited by COMPILE.IF because it's
invoked from within COMPILE.FUNCTION or whatever.  With lexical scope,
you end up having to invent a simulated-dynamic-scope mechanism to
allow the rest of the compiler to find the right symbol table.  It's
easy to write a Pascal compiler in Logo because the dynamic scope at
compile time matches the lexical scope at run time.