使用setf、defvar、let和作用域分配变量

6

所以,我从setq and defvar in lisphttp://www.cs.ucf.edu/courses/cop4020/spr2006/plsetup.htmlIn Lisp, how do I fix "Warning: Assumed Special?"等地方阅读了有关setf和defvar之间的区别。 所以我决定尝试一下这个想法:

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

好的,我知道setf应该用于更改现有变量的值,但在SBCL中未定义的变量警告似乎处理得相当不错(尽管我已经阅读过不同的CL实现可能会以不同的方式处理这个问题,因此这并不是最好的做法)。

进入第二个测试:

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

根据链接,我将setf更改为defvar,我认为这应该可以同时创建和绑定变量。现在我的未定义变量警告被推到(print y)行...这是怎么回事?
作为一个次要的问题,我期望任何在函数内分配的变量的值都无法在函数外部访问,就像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

我猜这与common lisp处理作用域的方式有关,即defvar会创建一个“全局特殊变量”...所以我最后尝试了一次使用(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

在阅读了defvar、defparameter、setf和setq有什么区别之后,以下代码似乎可以正常工作:

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

再次重申我的问题:
1)setf,defvar和let是什么?
2)是否有一种方法可以使common lisp在函数内部作用域像python示例中那样?

2个回答

7

回答 2) DEFVAR 定义了一个变量,但它并未被执行。因此编译器不知道在编译DEFUN形式时出现在print表单中的变量。它也是在DEFUN内部的。因此它不在顶层。作为顶级的形式,编译器将识别DEFVAR并意识到y是一个全局特殊变量。

再次重申我的问题:1) setf、defvar和let究竟发生了什么? 2) 是否有办法让common lisp像python示例一样将函数内的变量作用域限定在函数内部?

1)SETF设置变量的值,但不定义它。如果该变量未定义,则Common Lisp标准实际上并没有说明会发生什么。大多数Common Lisp实现都会执行某些有用的操作。通常,它被执行,就好像该变量已被声明为特殊变量(因此您还会收到警告)。

DEFVAR用作顶级形式(通常不在函数内部),以定义全局特殊变量。由于DEFVAR将变量名称声明为特殊变量,因此使用星号*将变量写成非常有用的约定:在变量名前后都加上星号*y*而不仅仅是y

LET定义了本地变量的作用域。

2) Common Lisp函数有参数列表来引入变量。除此之外,它们不定义变量作用域。如果要在函数内引入局部变量,请使用LET

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

Is

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

再次强调:函数不会为变量提供作用域,因此在函数内部分配变量将自动将其定义为函数本地变量。请改用LET

还要注意,LET主要是语法糖:(let ((a 1) (b 2)) (+ a b))基本上与((lambda (a b) (+ a b)) 1 2)相同。只是以一种更适合人类阅读的方式编写了一个简单的函数应用程序。

Common Lisp还支持旧语法:

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

上面定义了一个本地变量 X,就像 LET 一样。

关于旧语法和使用let的问题,哪种更受青睐?例如(defun foo () (let ((x 10) (y 5)) (print (+ x y)))) 和 (defun foo (&aux (x 10) (y 5)) (print (+ x y))) - user1922460
1
@RyanMoore:是的,永远不要使用&aux,总是使用let - Vatine
@RyanMoore,在代码中我看到 &aux 几乎没有被使用,而 let 则很常见。同时,有一个比函数更小的 let 也并不少见。 - Samuel Edwin Ward

4

简单明了地说:

1)setf、defvar和let的真正作用是什么?

  • defvardefparameter用于声明和设置全局特殊变量。只有当名称已经绑定时,它们才有所不同。

  • setf用于赋值(给特殊变量、词法变量和其他可能是自定义的setfable位置)。

  • let(和let*)创建在其主体内可见的新变量绑定。它可以创建词法或特殊绑定,这取决于(全局或本地)声明。如果没有特殊声明,则将创建词法绑定。

2)是否有办法让Common Lisp像Python示例一样在函数内部限定变量的作用域?

将代码放在let的主体中,这样绑定就可见:

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

谢谢你的回答。你认为在(let ...)中包含整个函数定义会有什么问题吗?另外,感谢你指出(incf z 10)的错误。 - user1922460
在你的陈述中,“当名称未绑定时它们才不同”,你可能想说“绑定”。 - Elias Mårtenson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接