Search code examples
lispcommon-lispdynamic-bindinglexical-scope

How to Explain Lexical vs Dynamic Binding?


I read a relevant post on binding, however still have questions.

Here are the following examples I found. Can someone tell me if the conclusions are correct?

Dynamic Binding of x in (i):

(defun j ()
 (let ((x 1))
    (i)))

(defun i ()
   (+ x x))

> (j)
2

Lexical Binding of x in i2:

(defun i2 (x)
       (+ x x))

(defun k ()
   (let ((x 1))
       (i2 2)))

> (k)
4

No Global Lexical Variables in ANSI CL so Dynamic Binding is performed:

(setq x 3)

(defun z () x)

> (let ((x 4)) (z))
4

Dynamic Binding, which appears to bind to a lexically scoped variable:

(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7

Based on the above tests, CL first tries lexical binding. If there is no lexical match in the environment, then CL tries dynamic binding. It appears that any previously lexically scoped variables become available to dynamic binding. Is this correct? If not, what is the behavior?


Solution

  • (defun j ()
     (let ((x 1))
        (i)))
    
    (defun i ()
       (+ x x))
    
    > (j)
    2
    

    This is actually undefined behavior in Common Lisp. The exact consequences of using undefined variables (here in function i) is not defined in the standard.

    CL-USER 75 > (defun j ()
                   (let ((x 1))
                     (i)))
    J
    
    CL-USER 76 > (defun i ()
                   (+ x x))
    I
    
    CL-USER 77 > (j)
    
    Error: The variable X is unbound.
      1 (continue) Try evaluating X again.
      2 Return the value of :X instead.
      3 Specify a value to use this time instead of evaluating X.
      4 Specify a value to set X to.
      5 (abort) Return to top loop level 0.
    
    Type :b for backtrace or :c <option number> to proceed.
    Type :bug-form "<subject>" for a bug report template or :? for other options.
    
    CL-USER 78 : 1 > 
    

    As you see, the Lisp interpreter (!) complains at runtime.

    Now:

    (setq x 3)
    

    SETQ sets an undefined variable. That's also not fully defined in the standard. Most compilers will complain:

    LispWorks

    ;;;*** Warning in (TOP-LEVEL-FORM 1): X assumed special in SETQ
    ; (TOP-LEVEL-FORM 1)
    ;; Processing Cross Reference Information
    ;;; Compilation finished with 1 warning, 0 errors, 0 notes.
    

    or SBCL

    ; in: SETQ X
    ;     (SETQ X 1)
    ; 
    ; caught WARNING:
    ;   undefined variable: COMMON-LISP-USER::X
    ; 
    ; compilation unit finished
    ;   Undefined variable:
    ;     X
    ;   caught 1 WARNING condition
    
    
    (defvar x 1)
    (defun f (x) (g 2))
    (defun g (y) (+ x y))
    > (f 5)
    7
    
    Dynamic Binding, which appears to bind to a lexically scoped variable
    

    No, x is globally defined to be special by DEFVAR. Thus f creates a dynamic binding for x and the value of x in the function g is looked up in the dynamic environment.

    Basic rules for the developer

    • never use undefined variables
    • when using special variables always put * around them, so that it is always visible when using them, that dynamic binding&lookup is being used. This also makes sure that one does NOT declare variables by accident globally as special. One (defvar x 42) and x will from then on always be a special variable using dynamic binding. This is usually not what is wanted and it may lead to hard to debug errors.