Assigning variables with setf, defvar, let and sco

2019-02-18 02:57发布

问题:

So, I have read from
setq and defvar in lisp,
http://www.cs.ucf.edu/courses/cop4020/spr2006/plsetup.html, and
In Lisp, how do I fix "Warning: Assumed Special?"
among other places about the difference between setf and defvar. So I decided to play around with the idea a bit:

CL-USER> (defun foo ()
       (setf x 10)
       (print x))

; in: DEFUN FOO
;     (SETF X 10)
; ==>
;   (SETQ X 10)
; 
; caught WARNING:
;   undefined variable: X
; 
; compilation unit finished
;   Undefined variable:
;     X
;   caught 1 WARNING condition
FOO
CL-USER> x
; Evaluation aborted on #<UNBOUND-VARIABLE X {10040F1543}>.
CL-USER> (foo)

10 
10
CL-USER> x
10

Okay, I know that setf should be used to change the value of an existing variable, but the undefined variable warning seems to be handled pretty well in SBCL (though I have read that different CL implementations may handle this differently, thus it isn't the best thing to do).

Enter the second test:

CL-USER> (defun bar ()
       (defvar y 15)
       (print y))

; in: DEFUN BAR
;     (PRINT Y)
; 
; caught WARNING:
;   undefined variable: Y
; 
; compilation unit finished
;   Undefined variable:
;     Y
;   caught 1 WARNING condition
BAR
CL-USER> y
; Evaluation aborted on #<UNBOUND-VARIABLE Y {10045033D3}>.
CL-USER> (bar)

15 
15
CL-USER> y
15

As per the links, I changed the setf to defvar which I think should create and bind the variable all at once. Now my undefined variable warning gets pushed into the (print y) line ... what is going on here?

As a secondary question, I am expecting the values of any variables assinged within a funciton to be inaccessible outside of the function, as is the case in Python:

>>> def foo():
...     x = 10
...     print x
... 
>>> foo()
10
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

I am guessing this has something to do with the way common lisp deals with scope, ie defvar creates a "global special variabe" ... So I tried one last time with (let ...)

CL-USER> (defun baz () (let ((z 10)) (print z)) (incf z 10) (print z))

; in: DEFUN BAZ
;     (INCF Z 10)
; --> LET* 
; ==>
;   (SETQ Z #:NEW0)
; 
; caught WARNING:
;   undefined variable: Z
; 
; compilation unit finished
;   Undefined variable:
;     Z
;   caught 1 WARNING condition

And after reading What's difference between defvar, defparameter, setf and setq, this one seems to work right:

CL-USER> (defun apple ()
       (defparameter x 10)
       (print 10))

APPLE
CL-USER> x
; Evaluation aborted on #<UNBOUND-VARIABLE X {1004436993}>.
CL-USER> (apple)

10 
10
CL-USER> x
10

Just to reiterate my questions: 1) what is really going on with setf, defvar and let?
2) is there a way to get common lisp to scope the variables inside a function as in the python example?

回答1:

answering 2) DEFVAR defines a variable. But it has not been executed. So the compiler does not know about the variable in the print form - when compiling the DEFUN form.. It's also inside a DEFUN. Thus it is not on the top-level. As a top-level form the compiler would recognize the DEFVAR and would notice that y is a global special variable.

Just to reiterate my questions: 1) what is really going on with setf, defvar and let? 2) is there a way to get common lisp to scope the variables inside a function as in the python example?

1) SETF sets a variable value, but does not define it. If that variable is undefined, then the Common Lisp standard does not really say what happens. Most Common Lisp implementations will do something useful. Typically it gets executed as if the variable would have been declared special (thus you also get a warning).

DEFVAR is used as a top-level form (usually not inside functions) to define global special variables. Since DEFVAR declares the variable name to be special, it is a very useful convention to write a variable with stars around it: *y* instead of just y.

LET defines a scope for local variables.

2) Common Lisp functions have the parameter list to introduce variables. Other than that they don't define a variable scope. If you want to introduce a local variable inside a function, use LET.

>>> def foo():
...     x = 10
...     print x

Is

(defun foo ()
  (let ((x 10))
    (print x)))

Again: a function does not provide a scope for variables, such that assigning a variable inside a function will automagically define it to be function-local. Use LET instead.

Note also that LET is syntactic sugar, mostly: (let ((a 1) (b 2)) (+ a b)) is basically doing the same as ((lambda (a b) (+ a b)) 1 2). It's just a simple function application written differently to improve it for the human reader.

There is also support in Common Lisp for an older syntax:

(defun foo (&aux (x 10))
  (print x))

Above defines a local variable X, just like a LET would do.



回答2:

I'll keep it short and simple:

1) what is really going on with setf, defvar and let?

  • defvar and defparameter are used to declare and set globally special variables. They only differ when the name is already bound.

  • setf is used for assignment (to special variables, lexical variables, and other – possibly custom – setfable places.)

  • let (and let*) creates new variable bindings that are visible inside its body. It may create either lexical or special bindings, depending on (global or local) declarations. If there are no special declarations, lexical bindings will be created.

2) is there a way to get common lisp to scope the variables inside a function as in the python example?

Put the code inside let's body, where the binding is visible:

CL-USER> (defun baz ()
           (let ((z 10))
             (print z)
             (incf z 10) ; i.e. (setf z (+ z 10))
             (print z)))
BAZ
CL-USER> (baz)

10 
20 
20
CL-USER> z
; Evaluation aborted on #<UNBOUND-VARIABLE #x186C611E>.