lispcommon-lispsbclclispland-of-lisp

What is the difference between CLISP and SBCL in handling closures and lexical variables?


(Edit 2025-02-27: I should note that there isn’t a difference between how CLISP and SBCL handle this situation the way I initially thought there was; see the comments to this question, and the answer that I accepted. Hopefully this will still be useful to people figuring out how Common Lisp in general handles special and lexical variables.)

So I’ve been going through the 2011 book Land of Lisp, but using SBCL instead of CLISP (CLISP is what the book recommends). I’ve encountered a few hiccups, involving changing code that uses CLISP-specific functions to their SBCL equivalents, but nothing I couldn’t handle so far… until I reached this part.

In Chapter 15, on page 326, the book instructs you to try the following code in the REPL:

(defparameter *foo* (lambda () 5))

…and then:

(funcall *FOO*)

The result, of course, is 5. But then, it has you try this more complicated example:

(defparameter *foo* (let ((x 5))
                      (lambda ()
                        x)))

…and then:

(funcall *foo*)

The book says the result of this should be 5, and indeed, that’s what happens in CLISP. But the result I get from SBCL is this:

#<HASH-TABLE :TEST EQL :COUNT 1 {100134C403}>

Wait… what?!

Now, back in chapter 9, the book had me make a hash table with (defparameter x (make-hash-table)) and then add an entry to it with (setf (gethash 'yup x) '25), so that’s why the global value of x is set to a hash table with one entry. But why is SBCL using the global value of x, here, when CLISP uses the lexically scoped value (5)?

This isn’t a problem for me to fix, so much as it is a difference I’m pretty sure I’ll need to understand to get through the rest of the book. I can’t quite get my head around this concept of closures and lexical scoping and whatnot, yet, and this is the section that’s supposed to explain that, but now I’m even more confused. Can somebody walk me through what is happening, here?


Solution

  • The variable *foo* is defined as a special variable.

    CL-USER 1 > (defparameter *foo* (lambda () 5))
    *FOO*
    
    CL-USER 2 > (funcall *FOO*)
    5
    

    The variable x is defined as a special variable.

    CL-USER 3 > (defparameter x (make-hash-table))
    X
    
    CL-USER 4 > (setf (gethash 'yup x) '25)
    25
    

    The LET expression binds the special variable x to 5 and returns a closure with no arguments. The closure returns the value of the special variable x.

    CL-USER 5 > (defparameter *foo* (let ((x 5))
                                      (lambda ()
                                        x)))
    *FOO*
    

    The closure gets called. x looks up the current dynamic binding (not the lexical binding, since x was defined to be a special variable). The current dynamic binding for x is the global value, which is the hash-table.

    CL-USER 6 > (funcall *foo*)
    #<EQL Hash Table{1} 801005C74B>