我正在使用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)))
在这两种情况下,我似乎都取得了相同的结果;这里使用setq
和let
有什么区别?为什么我不能在需要setq
的所有地方使用let
(因为它在语法上很容易)?
答案 0 :(得分:25)
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
隐藏变量并不会影响阴影变量的值;行x
中的d
是外x
引入的let
,其值未发生变化。 setq
仅影响调用它的变量。此示例显示setq
与局部变量一起使用,但也可以使用特殊变量(意思是,动态范围,通常使用defparameter
或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中的条目并遵循交叉引用,尤其是词汇表条目。例如,来自setq
和let
的HyperSpec的简短描述包括:
您可能想要阅读有关变量和绑定的更多信息。 let
和let*
对动态变量和special
声明也有一些特殊行为(但您可能暂时不需要知道这一点),在某些情况下(如果变量实际上不是一个变量,setq
实际上等同于setf
,那么你可能不需要知道一段时间。 HyperSpec有更多细节。
Stack Overflow上有一些不太重复的问题可能有助于理解Common Lisp中可用的各种变量定义和赋值运算符的使用:
答案 1 :(得分:2)
让我们几乎总是将函数定义中的变量绑定在一起 - 除非在极少数情况下您希望该值可用于同一范围内的其他函数。
我喜欢emacs lisp手册中的描述:
let用于将符号附加或绑定到值,使得Lisp解释器不会将变量与不属于函数的同名变量混淆。
要理解为什么需要特殊形式,考虑你拥有一个通常称为“{1}}房子”的房屋的情况,他可能指的是他的房子,而不是你的房子,也就是说,到另一所房子。
如果你的朋友指的是他的房子而你认为他指的是你的房子,你可能会有些混乱。如果在一个函数内部使用的变量与在另一个函数内部使用的变量具有相同的名称,并且这两个函数不打算引用相同的值,则Lisp中可能会发生同样的事情。 let特殊形式可以防止这种混淆。
- http://www.gnu.org/software/emacs/manual/html_node/eintr/let.html
答案 2 :(得分:1)
(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此行为是非标准的,但这是许多流行实现中发生的情况。无论它是否具有相当可预测性,由于其他原因,它被认为是一种不好的做法,大多数情况都是因为这些问题会阻碍您使用全局变量。
答案 3 :(得分:0)
只要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>