Kaz Kylheku
2024-01-15 18:31:30 UTC
... I never used Maclisp or Lisp 1.5 so I don't know how their
scoping worked.
MacLisp was weird. You typically debugged your program using the
MacLisp interpreter because it made the debugging cycle faster, and the
interpreter was purely dynamicly scoped. But when the MacLisp compiler
compiled your code, all the variables you hadn't declared SPECIAL became
lexical! I know that sounds crazy, but because MacLisp didn't really
support closures, it wasn't too hard to write code in a style such that
it didn't really matter whether variables were dynamic or lexical.
Assuming that the interpreter in the appendix of the Lisp 1.5 manual, I
would say that Lisp 1.5 was lexical. But I'm not 100% certain that that
interpreter really reflects the system's true semantics. (Does the
source code for the Lisp 1.5 system still exist anywhere?)
Now you have me wondering about the scoping rules of Church's lambda
calculus, or if the concept even applies. At first impression it seems
lexical.
It's definitely lexical, but you are right to wonder if the distinction
even applies. Our notion of "dynamic" scoping seems pretty closely tied
to the workings of the typical applicative order Lisp evaluator. I
imagine Church himself would be pretty puzzled by our notion of dynamic
scoping...
Even though LC is lexical, I suspect that examples of LC that breakscoping worked.
MacLisp was weird. You typically debugged your program using the
MacLisp interpreter because it made the debugging cycle faster, and the
interpreter was purely dynamicly scoped. But when the MacLisp compiler
compiled your code, all the variables you hadn't declared SPECIAL became
lexical! I know that sounds crazy, but because MacLisp didn't really
support closures, it wasn't too hard to write code in a style such that
it didn't really matter whether variables were dynamic or lexical.
Assuming that the interpreter in the appendix of the Lisp 1.5 manual, I
would say that Lisp 1.5 was lexical. But I'm not 100% certain that that
interpreter really reflects the system's true semantics. (Does the
source code for the Lisp 1.5 system still exist anywhere?)
Now you have me wondering about the scoping rules of Church's lambda
calculus, or if the concept even applies. At first impression it seems
lexical.
It's definitely lexical, but you are right to wonder if the distinction
even applies. Our notion of "dynamic" scoping seems pretty closely tied
to the workings of the typical applicative order Lisp evaluator. I
imagine Church himself would be pretty puzzled by our notion of dynamic
scoping...
under dynamic scope have to be either incorrect, or convoluted.
What I'm referring to by "incorrect" is the presence of unbound
references. Some function (lambda (x) y) occurs where y is not
bound to anything, and that is then passed and called somewhere where y
is dynamically bound. This "breaks" in the sense that it's ill
formed under normal LC, but dynamic LC accepts it.
The other cases deal with references to the wrong parameter.
Wrong references happen in two ways:
- the same identifier is reused for two different purposes, which
"cross wires" due to the scoping.
- the same variable is activated multiple times, which are expected
to be different instances but aren't.
The first instance is easier to first show with bound functions,
like:
(define fun (x) (x x))
(lambda (x)
(fun (lambda (y) x)))
Where inside fun, the x references are to its own parameter,
so the (lambda (y) x) gets confused. We can get rid of the define:
(lambda (x)
((lambda (x) (x x)) (lambda (y) x)))
OK, but when we do this, this becomes confusing regardless of
the scoping discipline, due to the reuse of the parameter name!
All the variables in the example are singly instantiated, so
the only problem is the naming; if we make it:
(lambda (z)
((lambda (x) (x x)) (lambda (y) z)))
the problem goes away and it now means the same thing under
dynamic scope.
The remaining category is problems where the LC breaks because a the
same variable is instantiated multiple times, and those instantiations
are not separately being captured by a lambda, since there is
actually only one variable.
In Lisp, it's easy to come up with examples: such as the difference
between a loop construct which steps the dummy variable versus one
which freshly binds it.
People are running into this even in Blub languages. E.g. Go language
recently fixed it foreach-type looping construct to bind a fresh
variable, under the belief that this is what programmers expect.
Under dynamic scope, it won't make a difference. You can pretend to bind
a fresh variable, but it's really equivalent to stepping, since there is
only one variable. In Common Lisp, if our implementation has a DOLIST
loop that freshly binds, but we use a special variable as the dummy,
then that is reduced to being equivalent to mutating DOLIST.
It's not obvious to me how to make a simple example of this in lambda
calculus. You don't have loops that can step a variable versus bind.
You have to use recursion in order to evaluate the same functions
multiple times, with different values of free variables that have
to be demonstrably captured.
I can see why it might not have been obvious to MacCarthy that saving
and restoring variables isn't enough for lambda-calculus-like
computation.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.