Difference between LET and SETQ?
Asked Answered
E

4

33

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?

Edelweiss answered 28/9, 2013 at 13:44 Comment(1)
If setq is "syntactically easy", why does it cause the reader of the code to have questions, like "what is its scope?"Cooley
B
37

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:

Borodin answered 28/9, 2013 at 14:14 Comment(2)
It's worth noting that although SBCL (and CCL) complain about code that setq's an undefined symbol, as it happens, they still go ahead and define the symbol, producing the behavior described in @wvxvw's answer when add_using_setq is run. Although this behavior is not required by the standard, it has a long history. I have old textbooks that teach you to define global variables using setq.Apogamy
Well, come to think of it, I'm not sure that the effects of setq outside of the top level have a long history of use. Maybe not.Apogamy
C
5

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

Carriecarrier answered 28/9, 2013 at 14:14 Comment(0)
H
2

(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.

Hairsplitting answered 28/9, 2013 at 14:2 Comment(19)
"(setq x y) globally assigns a new value y to the variable designated by the symbol x" is not true. setq is used to assign a value toa variable, and a variable may be local (lexical scope, e.g., with let and not declared special) or global (e.g., defined with defparameter or defvar.Borodin
@JoshuaTaylor you misquoted me. If you read further it says that it optionally creates such variable. I.e. the intention was to say that it will assign a new value to whatever variable was defined and, if it was not defined, then the global variable will be created. Maybe not the best way to word it, but I accept improvements. You are welcome to edit the answer if you know a better way to say that.Hairsplitting
The behavior of setq when a variable isn't defined is not defined behavior. It's implementation dependent, and it portable programs can't depend on them. E.g., see Rainer Joswig's answer to another question: "It is required for portable code that one sets variables that are already declared. The exact effect of setting a not declared variable is undefined by the standard."Borodin
@JoshuaTaylor this is interesting. Although I don't have a copy of the standard, Hyperspec doesn't mention this kind of situation. It also doesn't allow setq to throw. I'll rephrase the answer to reflect this.Hairsplitting
As long as we're picking nits :), it doesn't make much sense to talk about a "package-level variable," either. Lexical variables are identified in source by symbols, but there's no need to preserve, after compilation, the symbol that named a variable. Special variables, on the other hand, are identified by symbols, and while symbols can have a package, that's the only sense that a variable might be associated with a package. If an implementation makes (setq foo::x 34) define a special variable, then the symbol foo::x accessible anywhere. (Not trying to be too picky, but looseBorodin
terminology can end up confusing beginners (e.g., who need clarification about setq and let) causing other problems down the road. Packages and symbol accessibility can be tricky enough for the seasoned Common Lisper; forming an incorrect mental model early on could lead to some headaches later.Borodin
@JoshuaTaylor Mmm... foo::x would be still "package-level" variable, just of the package foo. I don't think that calling it a "special" variable is very uninformative: who knows what's special about it? The term is so vague that not even Google is going to help you, you might not even recognize it as a special term. I think this is a problem of semantic nature, which transforms any definition into a Zen koan :) Hyperspec also mentions a possibility of applying setq to symbols which have a special macro expansion capabilities, which would make the explanation even less intelligible.Hairsplitting
"Special variable" is a technical term in Common Lisp; they're what have traditionally been called dynamic variables, or dynamically scoped variables. It's important to call them special, because the language calls them that. See, e.g., the entry on the special declaration, as well as the glossary entry for special variable. It's the first result Google provides when searching for hyperspec "special variable". :)Borodin
@JoshuaTaylor I find the explanation in the documentation to be exceptionally unintelligible and the choice of word to name it extremely poor; why can't I? :) On the other hand, "package-level" gives a better intuition into what actually happens. (There is really nothing special about these variables in no sense of this word, they are regular objects of the language, no more special then functions or atoms). Someone had a brainfart when invented the name - why should it be a guideline for me? :)Hairsplitting
Oh, sure, go and redefine the language that lets you redefine the language, why don't you? ;)Borodin
@wvxvw: I can only strongly support Joshua Taylor's objections. "Package-level" gives a totally wrong intuition about the behaviour of these variables. They are not lexical variables whose lexical scope is their package. They are dynamic variables and therefore their bindings exist for an extent rather than a scope. And what's package-level at all about some variable that gets introduced by some deeply nested let and an accompanying special declaration?Sixtasixteen
@Rörd what is package level about it? - it is accessible right away by all code defined in the same package, its name is qualified by the package it is created in. In order to access it from another package, you would need to know in what package it was defined. No sane person would use special declaration in the way you picture it. You may grep just any code on Github to get convinced.Hairsplitting
PS. I'm not just stubborn, there's a reason I insist. So far I saw three other programmers learning Common Lisp, all of them independently came up with very similar explanation to this phenomena (most people don't learn by memorizing documentation text). Some would call these variables global, but after more careful observation, had understood that the global name space is divided between packages; then they developed a concept of a variable defined in package name space. special came up later as technicality that helps accomplish this task.Hairsplitting
@wvxvw: The problem is that "package-level" tells you nothing about what's special about these variables, i.e. that they're dynamic variables, lets change their value for those lets' dynamic extent rather than their lexical scope. A better understanding is that defvar and defpackage globally define a symbol as the name of a variable, and that (interned) symbols belong to packages (and it wouldn't make much sense to use an uninterned symbol for a variable name).Sixtasixteen
@Rörd that's not a problem, because they aren't special. Naming them "special" was a mistake in the first place. Most programming languages today allow you having such variables. "Special" means "rare" or "exclusive", perhaps "suited for a very particular purpose", but none of that is true about these variables. They are exactly the opposite, they are very common, they serve multitude of purposes and so on.Hairsplitting
To further my point: in Python there are module-level variables, (prefixed with global.) There is a way to describe these variables as "prefixed with global", which would be equivalent to calling our subject "special variables" based on the notion of that in order to implement the desired function some magic was performed on a symbol denoting variable such that the environment would process it accordingly. Another, more general, way to describe the situation is to say that the variable is "global", or "module-level", which tells you what it does, rather then how it is implemented.Hairsplitting
@wvxvw: I'm increasingly getting the suspicion that you have no clue what dynamic variables are. They are definitely not at all the same as some lexical variable with global, module-, or package-level scope. The term "special" does not refer to their global resp. package-level scope, but their dynamic behaviour.Sixtasixteen
@Rörd you are welcome to suspect whatever you like. I will keep to what I think is correct, thank you for your concern.Hairsplitting
let us continue this discussion in chatSixtasixteen
C
0
  • 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>
Catamaran answered 1/11, 2017 at 4:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.