[comp.sys.ti.explorer] Shadowing Problem

cdc@uafhcx.uucp (C. D. Covington) (10/06/89)

   I have had a problem with shadowed variables being NIL in the called
function as exemplified by the following code.

(defvar some-var "Old Value")

(defun foo () 
  (let ((some-var "New Value"))
     (bar))
   )

(defun bar ()
  some-var)

  The variable some-var in the function bar ends up NIL if I simply load
the system using this code with (make-system ...) [I am using OS 4.0]
Recompiling bar explicitly with c-s-C somehow solves the problem.
Is order of compilation the problem?

C. David Covington
Assistant Professor, Electrical Engineering
University of Arkansas
Fayetteville, AR 72701

INTERNET cdc@uafhcx.uark.edu
(501)575-6583 campus office
     575-5379 research office
     575-3041 research lab

RICE@SUMEX-AIM.STANFORD.EDU (10/07/89)

Hello,



>>     I have had a problem with shadowed variables being
>>  NIL in the called function as exemplified by the
>>  following code.

>>  (defvar some-var "Old Value")

>>  (defun foo ()
>>    (let ((some-var "New Value"))
>>       (bar)) )

>>  (defun bar ()
>>    some-var)

>>    The variable some-var in the function bar ends up
>>  NIL if I simply load the system using this code with
>>  (make-system ...) [I am using OS 4.0] Recompiling bar
>>  explicitly with c-s-C somehow solves the problem.  Is
>>  order of compilation the problem?

>>  C.  David Covington Assistant Professor, Electrical
>>  Engineering University of Arkansas Fayetteville, AR
>>  72701

>>  INTERNET cdc@uafhcx.uark.edu (501)575-6583 campus
>>  office
>>       575-5379 research office 575-3041 research lab


My guess here is that you don't fully understand the
semantics of special variables and/or the compilation
dependency that is introduced by the proclamation of a
variable as special and/or defsystem/make-system

I believe that what you probably have in your code is, in
fact, the following:

File1:

(defvar some-var "Old Value")

File2:

(defun foo () 
  (let ((some-var "New Value"))
     (bar))
   )

(defun bar ()
  some-var)

;-------------------------------------------------------------------------------

;;; Defsystem decl:

(defsystem frob
  (:Module f1 (("File1")))
  (:Module f2 (("File2")))
  (:Compile-Load f1)
  (:Compile-Load f2)
)

;-------------------------------------------------------------------------------

a) The behaviour of defvar/defconst/defparameter is to
mark a variable as SPECIAL, this is done by making a
side-effect on the environment (on the compilation
environment if the file being compiled or the global
environment if the file is being loaded or the form is
being EVALed.)

b) In order for any function to perceive a special
proclamation, such as that performed by defvar, the
variable must be special within either the global
environment or the compilation environment of the file
being compiled.

c) If you have a file which does the following:

(defun foo () 
  (let ((some-var "New Value"))
     (bar))
   )

(defun bar ()
  some-var)

(defvar some-var "Old Value")  ;;; Note declaration after use.

then the function foo will not compile in such a manner
that some-var will be assumed special because the defvar
for some-var has not been performed by the time that the
definition for foo is compiled.  Note: CL does not specify
block compilation of files, compilation happens form by
form with the appropriate side effects being performed on
the compilation environment after each form is compiled.

d) If you build an example such as the one above with the
defsystem shown then this will also not achieve the
desired effect because make-system always attempts to
compile a file with the minimum number of files loaded
into the global environment.  This is good practice since
it forces you to make explicit your compilation
dependencies.  In this case no compilation dependencies
have been specified between f1 and f2 so make-system
will compile File1 and File2 and will then load them.
Because each file compilation starts with a fresh
compilation environment, even if File1 is actually
compiled before File2 this will still not solve the
problem.

e) If you want to use make-system to make this problem go
away then you should express a compilation dependency
between the two files:

(defsystem frob
  (:Module f1 (("File1")))
  (:Module f2 (("File2")))
  (:Compile-Load f1)
  (:Compile-Load f2 (:fasload f1))
)

the same sort of thing will have to be done if you have
any other forms in your code that cause compilation
dependencies.  Examples of these are:

Defmacro / calls to the macro
Defstruct / uses of the defstruct accessors and type predicate
Defflavor / declatation of methods
Defclass / declaration of methods

Note: in the case of Defstruct the accessors will not be
open coded, but the correct semantics should apply.  The
compiler wil usually emit a warning totell you that you
have used the accessor function before it was declared.
If you are running a version before release 6 then you
will get an error if you attempt to compile a setf to a
defstruct slot before the defstruct has been defined.

It is generally a safe practice to use the
:Compile-Load-Init defsystem transformation if you have
macros or substs, since this transformation forces the
recompilation of the files that USE macros in the event of
a macro definition being changed.  Thus:

(defsystem frob
  (:Module f1 (("File1")))
  (:Module f2 (("File2")))
  (:Module macros (("Macros")))
  (:Compile-Load macros)
  (:Compile-Load f1)
  (:Compile-Load-Init f2 (macros) (:fasload f2 macros))
)

would be a safe thing to do if file module f2 had any
references to macros defined in module macros.

f) You can also get around this by the use of a special
declaration to tell the compiler what it cannot already
figure out from the environment.  Thus the File1 and File2
problem could also be solved by defining foo and bar in
the following way:

(defun foo () 
  (let ((some-var "New Value"))
     (declare (special some-var))
     (bar))
   )

(defun bar ()
  (declare (special some-var))
  some-var)


g) It's worth reading up on the semantics of special
variables to make dead sure that you understand what they
are doing, since they can be very confusing.  For
instance:

(defparameter fred 42)

(defun frib ()
  (print fred))

(defun frob (fred)
  (frib))

(frob 200) -> 200

Thus, the act of using a function argument whose name is
the same as special variable results in a special binding
of that variable.  This can be very confusing and is an
excellent reason in its own right for using the convention
of defining special variables with *s, i.e.  (defparameter
*fred* 42) so as to alert the programmer that special
binding is INTENDED when a function is defined like this.

Also, specials are bound within a given process.  So, if
you do the following:

(defparameter *fred* 42)

(defun bax ()
  (let ((*fred* 200))
    (break)))

(bax)

now, if you evaluate *fred* from the break loop it will be
200 as you expected, but if you go to a different listener
and evaluate *fred* it will have the value 42.  This is
because the binding for bax was made within the process in
which you called bax.  The only binding for *fred* in the
other process is the global binding of 42.  Similarly, if
*fred* was globally unbound [it was declareed simply by
saying (defvar *fred*)] then in the other process you would
get an error saying that *fred* was unbound if you tried
to print it.



I hope that this casts a little light on the matter.
Special variables are a corner of CL in which you can get
severely burned if you have an over simplistic model of
the semanntics, so it really is worth reading up in them.




Rice.