Search code examples
scopeclosurescommon-lisplexical-scopedynamic-scope

Why are arguments to an enclosing function not captured by closures in Common Lisp?


test.lisp:

(defvar test
  #'(lambda (var1)
      #'(lambda (var2)
          `((var1 . ,var1)
            (var2 . ,var2)))))
(defvar var1 'wrong)
(defvar var2 'wrong)

And in the REPL:

$ clisp -q -i test.lisp
;; Loading file test.lisp ...
;; Loaded file test.lisp
[1]> (funcall (funcall test 'right) 'right)
((VAR1 . WRONG) (VAR2 . RIGHT))

I thought that common lisp was supposed to be lexically scoped these days, so why is the value of var1 not captured by the inner lambda in test? How can I make sure it is captured?


Solution

  • This is visible when using an interpreter.

    Let's look at a compiler first:

    ? (load "/tmp/test.lisp")
    #P"/private/tmp/test.lisp"
    ? (funcall (funcall test 'right) 'right)
    ((VAR1 . RIGHT) (VAR2 . RIGHT))
    

    First the functions are getting compiled. The compiler assumes lexical binding. Then DEFVAR declares the variables VAR1 and VAR2 to be special (-> not lexical). In the executed code then, the code is still using lexical binding.

    You were using an Interpreter:

    First the functions are loaded. Nothing gets compiled. Then DEFVAR declares VAR1 and VAR2 as special.

    In the executed code then, the Interpreter is using dynamic binding - like you declared it. The Interpreter looks at the variables at runtime and sees that they are declared to be special.

    Difference:

    The compiler has generated the machine code before the special declaration. Thus at runtime it uses lexical binding.

    The interpreter looks at runtime at the existing declarations.

    Style

    If you want to avoid dynamic binding, don't declare variables to be special.