From rurban@sbox.tu-graz.ac.at Wed Feb 04 17:47:58 1998
Newsgroups: comp.cad.autocad
Subject: Re: using local variables???
From: Reini Urban [rurban@sbox.tu-graz.ac.at]
Date: Wed, 04 Feb 1998 16:47:58 GMT
By accident I'm right now comparing the various lisp models and
therefore could give you a more precise description of some difficult
terms.
"Extent" (= Life-time of symbols)
AutoLISP symbols have dynamic extent. That means there life-time and
values depends on their current run-time evaluation. As the code is
executed the symbols are created and die if they are declared locally.
Global symbols "die" when set to nil and when the garbage collector
(which runs automatically) frees their memory
cell. Local symbols are automatically set to nil when leaving the
context (the function).
"Shadowing"
If a new local symbol is created and a symbol with the same name already
exists, the old value is saved, the new symbol is created and when the
context is left (end of the function)
the old value is restored. That's called "shadowing".
"Environment"
The list of the current valid symbols is called "environment". Every
function with local symbols has its own environment, to be able to be
called recursively.
>Didier DUHEM wrote:
>> I think it is not a good idea to define functions within others;
>> some compilers ( Vital lisp) do not like them
Tom Berger [tberger@architektur.uni-kassel.de] wrote:
>It is quite common to define functions within other functions (i.e. lambda ....).
>VitalLISP can handle these structures very well (otherwise it wouldn´t be true
>LISP). Compiling these structures may cause security hazards, because there may be
>code that is generated at runtime and therefore may become visible in source form.
More about scope/extent and Vital Lisp:
---------------------------------------
Vital Lisp internally (Lisp++ or LPP) is a true lisp, but AutoLISP is
different in this matter. Vital Lisp's AUTOLISP mode is completely
compatible to the native AutoLISP.
Below are some examples that should explain the differences which are
for sure difficult to understand.
Use (function) in Vital Lisp
If you embed internal functions in (function (lambda args . body)
instead of (quote (lambda args . body) or '(lambda args . body)
you ensure that the the code is compiled at compile-time (to an unnamed
USUBR) and not evaluated at run-time.
"To help the compiler link and optimize unnamed functions, VILL provides
a special FUNCTION form. FUNCTION is in all aspects identical to QUOTE
except that it will tell the compiler to link and optimize the argument
just as if it were a built-in function or defun.
If you write
(mapcar
'(lambda (x) (* x x))
'(1 2 3))
the compiler is not able to optimize the quoted lambda expression.
However, if you write
(mapcar
(function (lambda (x) (* x x)))
'(1 2 3))
the compiler is able to optimize the internal function."
Scope/Extent & Shadowing
------------------------
AutoLISP had always dynamic scope for local symbols as all primitive
lisps. On the contrary true Lisp's like Vital Lisp's internal Lisp
(Lisp++) has lexical scoping for local symbols and functions.
The difference is not very important for AutoLISP programmers. The only
practical difference is when accessing locally defined functions with
overlapping symbol names, when the call to the locally defined function
is outside the lexical context.
Simple example for shadowing:
;;; correct shadowing of external symbol names
;;; in local symbols or arguments
(defun foo1 (a)
(defun bar1 (a)
(print (setq a 'inner-a))
nil
)
(bar1 a)
a
)
;;; Test, same in all lisps:
(foo1 'outer-a) ;=> INNER-A OUTER-A
;;; Differences in argument extend and the context of (eval):
;;; the comments explain AutoLISP
(defun foo2 (a) ; called with (foo2 'A): 'A => A
(defun bar2 (a)
(print (set a 'inner-a)) ; a copy of A is set to INNER-A shadowed
(print a) ; yes, it's still INNER-A
)
(bar2 'a) ; call it with 'A
(print a) ; 'A remains unmodified
(eval a) ; and 'A evals to A
)
(defun bar2 (a)
(print (set a 'outer-a))
a
)
;;; Testcases:
;;; AutoLISP and Vill's AutoLISP context:
(setq a 'outer-a); => OUTER-A
(foo2 'a) ; => INNER-A INNER-A A A
a ; => OUTER-A
(bar2 'a) ; => INNER-A INNER-A INNER-A (the inner bar2!)
a ; => OUTER-A
;;; any real Lisp (tested with LPP, xlisp 2+ and Allegro CL)
(setq a 'outer-a); => OUTER-A
(foo2 'a) ; => INNER-A A A INNER-A
a ; => INNER-A
(bar2 'a) ; => INNER-A A A
a ; => INNER-A
;;; Two differences:
;;; 1) (set a) in (bar2)
;;; should modify the global symbol whilst in AutoLISP
;;; the parameter is copied (shadowed) and therefore not attached.
;;; 2) (eval a) in (foo2) uses global context whilst in AutoLISP
;;; the context for A is local. See the next example.
Note: The outer (bar2) is never called!
[this was added in a followup]
On the call (foo2 'a) the inner (defun bar2 ...) is called which overwrites the
previous outer definition, though textually it's after (defun foo2...).
That's why I have choosen that example:
At run-time the inner bar2 overwrites the outer bar2 definition.
If you understood that, that's the first way to understand lexical vs. dynamic scope,
which will be explained now:
;;; Difficult (eval) example:
;;; "global vs. local context" to explain 2) better
(defun foo3 (/ a funclist)
(setq a 'local-a)
;; construct an anonymous function at run-time
(setq funclist
;; LAMBDA is our function name. it could be any other but for
;; historical reasons it's called lambda. It could be nil or
;; anything else
'(lambda (a) ; In AutoLISP the local-a,
(print a) ; else the outer-a
(print (set a 'inner-a)) ; modify it
)
)
(eval (list funclist 'a)) ;with local A as argument
)
;;; The list looking like
;;; (name (arg) ...) is constructed by (defun name (arg) ...)
;;; On a function call like (name arg) name expands to the funclist
;;; Therefore (name arg) expands to (list funclist arg)
;;; which is here (list '(lambda (a) ...) a)
;;; The same approach would be
;|
(defun lambda (a)
(print a))
(lambda a)
;; or
(eval (list lambda a))
|;
;;; Test it in AutoLISP:
(setq a 'outer-a) ; => OUTER-A
(foo3) ; => LOCAL-A INNER-A INNER-A
;;; and test it in a real lisp (but replace "/" with "&aux" first)
(setq a 'outer-a) ; => OUTER-A
(foo3) ; => OUTER-A INNER-A INNER-A
;; you see:
;; AutoLISP's (eval) uses the local environment,
;; normal Lisp (eval) a global environment.
Dynamical Binding vs. Lexical Binding [added in a follwup]
-------------------------------------
Just to compare the dynaymic model with the lexical model
it's easier not to look at arguments to functions.
global vs. local variables will do it easier.
(defun foo4 (/ a)
(setq a 'local-a)
(defun bar4 ()
(princ a)
nil)
(princ a)
)
; with dynamical binding (AutoLISP) the current a (global-a) is printed
(setq a 'global-a)
(foo4) => LOCAL-A LOCAL-A
(bar4) => GLOBAL-A NIL
; with lexical binding (CL) the a from the functions lexical
; context (local-a) is printed.
; when bar4 is defined in the context of foo4, the value of a is local-a then.
; on every call to bar4 a is expanded to local-a.
(setq a 'global-a)
(foo4) => LOCAL-A LOCAL-A
(bar4) => LOCAL-A NIL
you may think of an compiled lisp which needs the values of local
variables at compile time, because otherwise dynamic (= special)
variables have to be retrieved from the global environment (which is a
big hashtable).
it's much faster and needs less memory to store just the value LOCAL-A
inside the compiled function bar4
; but you may define a as special variable (means dynamic), then
; a is in dynamic context and you get the actual value.
(defun foo4-special (/ a)
(setq a 'local-a)
(defun bar4-special ()
(declare (special a)) ; a is now a dynamic variable
(princ a)
nil)
(princ a)
)
(setq a 'global-a)
(foo4-special) => LOCAL-A LOCAL-A
(bar4-special) => GLOBAL-A NIL
Summary
-------
In fact the difference is only important if you use functions as
arguments which rely on the global variables or variables declared in an outer
function. (which is dirty style anyway).
In AutoLISP you may get different results if you are in a context where
your global symbol is currently shadowed. Esp. if you indent to use some
kind of object model, like CLOS, you'll get problems. In my eyes the
dynamic evaluation model is easy to understand.
The lexical model means that local symbols could only be shadowed by
symbols which where active in the environment when the function was
CREATED (normally global), but in AutoLISP you may get shadowed symbols
in your environment in which the function is CALLED (and this will
change a lot!).
In short:
Lexically scoping ensures you to have always the same side effects. In a
dynamic model (AutoLISP) it depends on the current environment.
References:
-----------
See also the CLtL2 lisp reference manual which explains this topic in
greater detail:
http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html
and the ANSI CL specification on shadowing & scope/extent:
http://www.harlequin.com/education/books/HyperSpec/Body/sec_3-1-5.html
Hope, this is clear now. At least I for myself got it now.
I only needed it to correctly mimic some common lisp functionality in
plain AutoLISP, like case, typecase, setf, getf and defstruct for now.
Also some test tools.
A simple message passing object model (no CLOS for now) is in
development, also some kind of type system.
Hey! Everything without defmacro! (All arguments have to be quoted)
It will be published when time is ready.
defmacro would really enable us to implement some kind of better object
model, which is very useful, especially for DCL or even MFC dialogs.
(why not?) but the dynamic scoping would be then a harder problem.
-- Reini Urban
AutoCAD stuff at http://xarch.tu-graz.ac.at/autocad/
AutoCAD: Visual LISP > Apuntes para un Curso... > Programación de Aplicaciones Gráficas > 2. Técnicas Fundamentales > 2.1. Tipos de Datos > 2.1.1. Átomos >