LET和SETQ之间的区别?

时间:2013-09-28 13:44:04

标签: lisp common-lisp

我正在使用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)))

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

4 个答案:

答案 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与局部变量一起使用,但也可以使用特殊变量(意思是,动态范围,通常使用defparameterdefvar定义:

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

请注意,setq并非(可移植)创建变量,而letdefvardefparameter和& 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中的条目并遵循交叉引用,尤其是词汇表条目。例如,来自setqlet的HyperSpec的简短描述包括:

  • SETQ

      

    将值分配给variables

  • LET

      

    让我们创建新变量bindings并执行一系列   使用这些绑定的表单。

您可能想要阅读有关变量和绑定的更多信息。 letlet*对动态变量和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)

  • SETQ

只要Lisp仍在运行,你就可以从符号中获取符号的值。 (它将值赋给符号)

  • LET

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