30

我正在使用 GCL 在 Ubuntu 上编程。从各种来源的 Common Lisp 文档中,我了解到let创建局部变量并setq设置现有变量的值。在以下情况下,我需要创建两个变量并将它们的值相加。

使用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))

使用let

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

在这两种情况下,我似乎都取得了相同的结果;setqusing和lethere有什么区别?为什么我不能setq在我需要使用的所有地方使用(因为它在语法上很容易)let

4

4 回答 4

37

setq 为变量赋值,同时let引入新的变量/绑定。例如,看看发生了什么

(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

外部let创建了一个局部变量x,内部let创建了另一个局部变量来遮蔽内部变量。请注意,使用let阴影变量不会影响阴影变量的值;xin lined是由xexternal 引入的,let它的值没有改变。setq只影响调用它的变量。此示例显示setq与局部变量一起使用,但它也可以与特殊变量一起使用(意思是动态范围,通常使用defparameteror定义defvar

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

请注意,setq它不会(可移植地)创建变量,而let, defvar, defparameter, &c. 做。使用不是变量(尚未)的参数调用时的行为setq未定义,由实现决定做什么。例如,SBCL 大声抱怨:

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

当然,更好地理解这些概念的最好方法是阅读和编写更多的 Lisp 代码(随着时间的推移),阅读 HyperSpec 中的条目并遵循交叉引用,尤其是词汇表条目。例如,来自 HyperSpec 的简短描述setq包括let

您可能想阅读有关变量和绑定的更多信息。 let并且let*对动态变量和声明也有一些特殊的行为special(但您可能暂时不需要知道),并且在某些情况下(您可能暂时不需要知道)当变量实际上不是变量,setq实际上等价于setf. HyperSpec 有更多细节。

Stack Overflow 上有一些不完全重复的问题,尽管如此,它们可能有助于理解 Common Lisp 中可用的各种变量定义和赋值运算符的使用:

于 2013-09-28T14:14:20.237 回答
5

Let 几乎总是您在函数定义中绑定变量的方式——除了在极少数情况下您希望该值可用于同一范围内的其他函数。

我喜欢 emacs lisp 手册中的描述:

let 用于将符号附加或绑定到值,这样 Lisp 解释器不会将该变量与不属于函数的同名变量混淆。

要理解为什么需要特殊形式,请考虑您拥有通常称为“房屋”的房屋的情况,例如“房屋需要绘画”这句话。如果您正在拜访一位朋友,而您的主人指的是“房子”,那么他可能指的是他的房子,而不是您的房子,也就是说,他指的是另一所房子。

如果您的朋友指的是他的房子,而您认为他指的是您的房子,那么您可能会感到困惑。如果在一个函数内部使用的变量与在另一个函数内部使用的变量具有相同的名称,并且两者并不打算引用相同的值,那么在 Lisp 中可能会发生同样的事情。let 特殊形式可以防止这种混淆。

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

于 2013-09-28T14:14:12.673 回答
2

(setq x y)y为符号指定的变量分配一个新值x,可选地定义一个新的包级变量1。这意味着在您调用之后,add_using_setq您将在当前包中拥有两个新的包级变量。

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

将打印3 4- 不太可能达到预期的结果。

相比之下,当您使用 时let,您只会在函数执行期间为符号指定的变量分配新值,因此此代码将导致错误:

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

考虑let等同于以下代码:

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

顺便说一句,您真的想查看其他程序员编写的代码,以了解如何命名或格式化事物。除了传统之外,它还具有一些您不想放弃的印刷属性。

1 这种行为是非标准的,但这是在许多流行的实现中发生的情况。尽管它是相当可预测的,但由于其他原因,它被认为是一种不好的做法,主要是所有相同的问题都会阻止您使用全局变量。

于 2013-09-28T14:02:02.573 回答
0
  • SETQ

只要 Lisp 仍在运行,您就可以将符号的值超出范围。(它将值分配给符号)

在 Lisp 完成评估表单后,您无法获得用 LET 定义的符号的值。(它将值绑定到符号并创建新的符号绑定)

考虑下面的例子:

;; 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>
于 2017-11-01T04:37:32.923 回答