[net.lang.lisp] shallow binding and deep binding

anderson@ittvax.UUCP (Scott Anderson) (11/22/83)

There was recently a very long article in net.lang.lisp (almost 800
lines) on why many lisps distiguish between the value of a symbol in
function position and in argument position (the EXPR property vs the
VALUE property).  In it, several people mention "shallow binding" and
"deep binding."  I'm not familliar with these terms.  Can anyone tell
me what they mean?

Thanks,

Scott D. Anderson
decvax!ittvax!anderson OR
yale-comix!ittvax!anderson

liz@umcp-cs.UUCP (11/24/83)

Shallow binding systems always keep the current value of atoms in
each atom's value cell.  When an atom is lambda bound (by entering
a function) the old value of the atom is saved on a stack somewhere
and the atom's value cell is set to the new value.  When the lambda
is exited, the old value of the atom is restored from the stack.
The advantage of a shallow binding system is quick access to the
value of an atom.

In a deep binding system, there is something called an alist which
saves all the current values of all the atoms.  The alist is simply
an assoc list of atoms and values.  When you need the value of an
atom, you have to assq down the list to the first occurrence of an
atom and get its value.  The disadvantage of deep binding is this
slow access time.  When an atom is lambda bound to a new value,
the atom and value are simply pushed onto the alist.  When the
lambda exits, the alist is popped back to where it was before the
lambda was entered.  The big advantage to deep binding is that you
can save the value of the alist and restore a context simply by
setting the alist to the saved value.  Closures and the like become
quite simple.

Most lisps these days use shallow binding.  The old Maryland lisp
that ran on our Univac used deep binding but with an interesting
twist.  Each atom also had a global binding cell which was always
checked first for a value.  The global cell could be set using
"csetq" and always overode any lambda binding.  That way you got
the efficiency of shallow binding when you wanted and the context
saving of deep binding when you needed...  I don't know what other
lisps may use deep binding, but the trend seems to be towards things
like invisible pointers (as in Lisp Machine lisp) static binding
(as in T) to get closures, etc, out of a shallow binding system.

				-Liz
-- 
Liz Allen, Univ of Maryland, College Park MD
Usenet:   ...!seismo!umcp-cs!liz
Arpanet:  liz%umcp-cs@CSNet-Relay

rpk@mit-vax.UUCP (11/29/83)

I don't know if, when a variable is lambda-bound under a deep-binding
scheme, the ``current'' value is put in the value cell and old value is left
on the stack.  If that is true, then a problem arises when the Lisp
environment supports multiprocessing.  Suppose one executes in process A

   (LET ((*BASE* 10.))
      (DO-IT))

where *BASE* is 8 globally, and process B looks at *BASE*.  Process B should
get the top level value of *BASE*, not the value that process A bound.
I would think that, instead, the new value would be pushed on the stack,
along with some bookeepping to find the previous value.

One way to speed up variable lookup that is used in MacLisp and ZetaLisp is
to tell the compiler that only ``special'' variables will really be
lambda-bound; all other local variables and arguments will be considered
``non-special''  This gives you efficiency in compiled code at the expense
of compatibility with the interpreter.  ``Special'' variable references are
turned into routines that do lookup on the binding stack by symbols; all
other variable references are turned into stack references.

Most Lisps are dynamically scoped, but Common Lisp will be lexically scoped.
The special/non-special distinction described above is >>almost<< compatible
with lexical scoping, since the default is that bindings from ``superior''
stack frames will not be inherited into the current stack frame.  Common
Lisp also allows ``fluid'' variable declaration, which is just the
``special'' declaration.
-- 
``Bob''  Robert P. Krajewski      ARPA: RpK@MC
			     MIT Local: RpK@OZ
                                  UUCP: ...!genradbo!mit-eddie!mitvax!rpk

tjt@kobold.UUCP (T.J.Teixeira) (11/29/83)

mit-vax!rpk appears to be confused about shallow-binding and
deep-binding.  In deep-binding, the current value is *always* kept on
the binding-stack, *never* in the value cell.  Some lisps that use
deep-binding do keep a value-cell for the "global" or "top-level" value
(e.g. Interlisp) and include a mechanism (often a hack) for accessing
this value (e.g. car of an atom is its top-level value in Interlisp).

Note that Interlisp does not require "special" declarations.  The
binding stack for Interlisp (at least for pdp-10's) has a pointer
to the atom being bound in the high half word and the value in the low
half word.  Compiled functions simply set up the high half word when
entered but are able to access locally defined variables as stack
offsets.  Deeply-bound lisps do act as rpk suggests and push the
new value on the stack along with some bookkeeping information.
The bookkeeping information is the name of the variable that
this binding affects and not how to find the previous value.  That
information is implicit in the order of the bindings on the stack.

A good way to characterize deep-binding and shallow-binding schemes is
that shallow-binding makes variable lookup fast, but binding (and
unbinding) expensive while deep-binding makes variable lookup slow, but
binding cheap.  i.e. in shallow binding, all variables are evaluated in
constant time by looking at the value cell.  To make a new binding, the
old value must be pushed on a stack, and the old value must be restored
when returning from that function (or past it using a non-local jump of
some type. e.g. after an error).  In deep binding, evaluating a
variable takes time proportional to the depth of the binding stack.
Making a new binding may be slightly cheaper if there is a good way to
set up the "high half-word" in the binding stack (otherwise you use the
instruction/memory access that you save by not having to push the old
binding when setting up the name in the binding stack).  However,
removing any number of local bindings can be done by simply resetting
the stack pointer.

There are two advantages of lexically scoped lisps in combination with
deep binding.  Firstly, lexical scoping limits the depth of the binding
stack to the depth of lexical nesting.  More importantly, the compiler
is able to efficiently access non-local variables as well as the local
ones since it knows the structure of all the lexically enclosing stack
frames as well as the structure of the local stack frame.
-- 
	Tom Teixeira,  Massachusetts Computer Corporation.  Westford MA
	...!{ihnp4,harpo,decvax,ucbcad,tektronix}!masscomp!tjt   (617) 692-6200

patcl@tekecs.UUCP (Pat Clancy) (11/29/83)

The definitions of the terms "shallowing binding" and
"deep binding" which I learned are different from those
given by umcp-cs!liz. Namely: deep binding requires a tree
of saved environments, while shallow only requires a
stack. The difference has to do with the binding of free
variables, eg:

	(csetq foo(lambda(x)
		(lambda()(car x))
			))
	
	(csetq bar(lambda(x y)
		((foo y))
			))

(please excuse the probably weird usage, I learned Lisp
on a Univac 1100!)

Now the result of: (bar '(a b) '(c d))
has two possible values:

deep binding:	c
shallow binding: a

The free variable is the "x" inside the function returned
by "foo". With deep binding, the alist branch created when
foo is being evaluated is saved, including the entry for x,
and this is the x which is found on evaluation of the inner
(unnamed) function (ie, the inner function is passed a "funarg"
including a pointer to the saved environment). With shallow
binding, the x created during foo's evaluation is lost when
foo exits (no alist tree, only a stack), so the x created
during evaluation of bar is found.

I have seen both this, and umcp-cs!liz's definitions in
the literature, so I believe they are both correct but
incompatible usages of these terms.

Pat Clancy
Tektronix