27
votes

I'm programming on Ubuntu using GCL. From the documentation on Common Lisp from various sources, I understand that let creates local variables, and setq sets the values of existing variables. In cases below, I need to create two variables and sum their values.

Using setq

(defun add_using_setq ()
  (setq a 3)  ; a never existed before , but still I'm able to assign value, what is its scope?
  (setq b 4)  ; b never existed before, but still I'm able to assign value, what is its scope?
  (+ a b))

Using let

(defun add_using_let ( )
  (let ((x 3) (y 4)) ; creating variables x and y
     (+ x y)))

In both the cases I seem to achieve the same result; what is the difference between using setq and let here? Why can't I use setq (since it is syntactically easy) in all the places where I need to use let?

4
If setq is "syntactically easy", why does it cause the reader of the code to have questions, like "what is its scope?"Kaz

4 Answers

36
votes

setq assigns a value to a variable, whereas let introduces new variables/bindings. E.g., look what happens in

(let ((x 3))
  (print x)      ; a
  (let ((x 89))
    (print x)    ; b
    (setq x 73)  
    (print x))   ; c
  (print x))     ; d


3   ; a
89  ; b
73  ; c
3   ; d

The outer let creates a local variable x, and the inner let creates another local variable shadowing the inner one. Notice that using let to shadow the variable doesn't affect the shadowed variable's value; the x in line d is the x introduced by the outer let, and its value hasn't changed. setq only affects the variable that it is called with. This example shows setq used with local variables, but it can also be with special variables (meaning, dynamically scoped, and usually defined with defparameter or defvar:

CL-USER> (defparameter *foo* 34)
*FOO*
CL-USER> (setq *foo* 93)
93
CL-USER> *foo*
93

Note that setq doesn't (portably) create variables, whereas let, defvar, defparameter, &c. do. The behavior of setq when called with an argument that isn't a variable (yet) isn't defined, and it's up to an implementation to decide what to do. For instance, SBCL complains loudly:

CL-USER> (setq new-x 89)

; in: SETQ NEW-X
;     (SETQ NEW-X 89)
; 
; caught WARNING:
;   undefined variable: NEW-X
; 
; compilation unit finished
;   Undefined variable:
;     NEW-X
;   caught 1 WARNING condition
89

Of course, the best ways to get a better understanding of these concepts are to read and write more Lisp code (which comes with time) and to read the entries in the HyperSpec and follow the cross references, especially the glossary entries. E.g., the short descriptions from the HyperSpec for setq and let include:

  • SETQ

    Assigns values to variables.

  • LET

    let and let* create new variable bindings and execute a series of forms that use these bindings.

You may want to read more about variables and bindings. let and let* also have some special behavior with dynamic variables and special declarations (but you probably won't need to know about that for a while), and in certain cases (that you probably won't need to know about for a while) when a variable isn't actually a variable, setq is actually equivalent to setf. The HyperSpec has more details.

There are some not-quite duplicate questions on Stack Overflow that may, nonetheless, help in understanding the use of the various variable definition and assignment operators available in Common Lisp:

3
votes

Let should almost always be the way you bind variables inside of a function definition -- except in the rare case where you want the value to be available to other functions in the same scope.

I like the description in the emacs lisp manual:

let is used to attach or bind a symbol to a value in such a way that the Lisp interpreter will not confuse the variable with a variable of the same name that is not part of the function.

To understand why the let special form is necessary, consider the situation in which you own a home that you generally refer to as ‘the house,’ as in the sentence, “The house needs painting.” If you are visiting a friend and your host refers to ‘the house,’ he is likely to be referring to his house, not yours, that is, to a different house.

If your friend is referring to his house and you think he is referring to your house, you may be in for some confusion. The same thing could happen in Lisp if a variable that is used inside of one function has the same name as a variable that is used inside of another function, and the two are not intended to refer to the same value. The let special form prevents this kind of confusion.

-- http://www.gnu.org/software/emacs/manual/html_node/eintr/let.html

1
votes

(setq x y) assigns a new value y to the variable designated by the symbol x, optionally defining a new package-level variable 1. This means that after you called add_using_setq you will have two new package-level variables in the current package.

(add_using_setq)
(format t "~&~s, ~s" a b)

will print 3 4 - unlikely the desired outcome.

To contrast that, when you use let, you only assign new values to variables designated by symbols for the duration of the function, so this code will result in an error:

(add_using_let)
(format t "~&~s, ~s" a b)

Think about let as being equivalent to the following code:

(defun add-using-lambda ()
  (funcall (lambda (a b) (+ a b)) 3 4))

As an aside, you really want to look into code written by other programmers to get an idea of how to name or format things. Beside being traditional it also has some typographic properties you don't really want to loose.

1 This behaviour is non-standard, but this is what happens in many popular implementations. Regardless of it being fairly predictable, it is considered a bad practice for other reasons, mostly all the same concerns that would discourage you from using global variables.

0
votes
  • SETQ

you can get the value of the symbol out of the scope, as long as Lisp is still running. (it assign the value to the symbol)

  • LET

you can't get the value of the symbol defined with LET after Lisp has finished evaluating the form. (it bind the value to the symbol and create new binding to symbol)

consider example below:

;; with setq
CL-USER> (setq a 10)
CL-USER> a
10

;; with let
CL-USER> (let ((b 20))
       (print b))
CL-USER> 20
CL-USER> b ; it will fail
; Evaluation aborted on #<UNBOUND-VARIABLE B {1003AC1563}>.
CL-USER>