LISP本地/全局变量赋值

时间:2012-09-08 18:58:13

标签: lisp common-lisp

如果我们定义类似

的函数
(defun foo(x)
  (setf x somevalue))

x被定义为局部变量还是全局变量?使用setf / q将值设置为全局。 如果它是全局的,任何人都可以告诉我如何在除let之外的lisp中定义局部变量吗?

谢谢!

考虑以下示例

(let ((x 0)) 
  (defun foo (y) 
    (when (equal x 0) (setq x y)) 
    (when (< x y) (setq x y))
    x))

当我向foo (foo 2)提供一些输入时,它返回2,如果我们再次使用(foo 1)执行该功能,它仍会返回2并且(foo 3)返回3.这就是我真正想要它做的。但问题是这怎么可能,因为如果我尝试从clisp终端访问函数外的变量x,我无法。如果我正在访问该函数再次出现,它似乎保留了x的先前值。

谢谢你!

3 个答案:

答案 0 :(得分:8)

要绘制与其他语言(如C,C ++,Java或Python)的并行,您的代码更改的是“局部变量”,即使这不是Lisper使用的措辞(Lisp用语中的措辞也是本地的“结合”)。

您可以使用函数参数(如示例)或使用某些标准格式(如:

)创建局部变量
  • (let ((x 12)) ...)
  • (do ((x 0 (1+ i))) ...)
  • (dotimes (x 10) ...)
  • (loop for x from 0 to 10 do ...)

另一方面,在您的实现中,有可能使用参数创建所有局部变量,而其他形式只是宏扩展到那些。例如:

(let ((x 10)) ...)

相当于

(funcall (lambda (x) ...) 10)

另请注意,确实在读取代码片段时,x在某种意义上可能是“全局变量”,因为它可能已被声明为特殊:

(defvar x 12)
;; ==> x

(defun bar ()
  (format t "The value of x is ~a" x))
;; ==> bar

(defun foo (x)
  (bar))
;; ==> foo

(foo 42)
The value of x is 42
;; ==> NIL

x
;; ==> 12

如果使用(defvar ...)声明变量“special”,它将以不同的方式处理:就像每次将它用作参数一样,或者以(let ..)形式使用它时代码将执行的操作保存当前值,使用新提供的值,然后在退出函数或let后恢复该值。

所以这些变量都是“全局的”(因为外部函数可以看到它们)而且也是本地的(因为在你的函数之后或让终止之前的值将被恢复)。

标准惯例是使用“耳罩”命名特殊变量,即在名称的开头和结尾都带有星号,如:

(defvar *x* 12)

这有助于谁读取您的代码以了解该变量是特殊的。请注意,这不是语言强制要求,任何名称都可以用于特殊变量。

与C,C ++,Java或Python中的特殊变量没有任何相似之处。

关于setqsetf的最后一点说明。这里的事情有点棘手,因为你需要了解较低级别的Lisp,看看为什么需要setq。如果您使用Common Lisp,那么您应该忘记setq并始终使用setf

setf是一个宏,在需要时会扩展到setq(但是setq在需要时也可以更改为setf(符号宏)让新手感到困惑。)

你的最后一个例子是“封闭”的情况。当您定义一个函数(使用(lambda ...)形式命名或未命名)时,该函数可以“捕获”可见的变量并在以后使用它们。通常显示的更简单的情况是“加法器”:

(defun adder (x)
  (lambda (y) (incf x y)))

此函数返回一个函数,该函数将继续将传递的值添加到内部累加器:

(let ((f (adder 10)))
  (print (funcall f 3))
  (print (funcall f 9))
  (print (funcall f 11)))

输出为13(10 + 3),22(13 + 9)和33(22 + 11)。

匿名函数“捕获”了局部变量x,即使退出adder函数也可以使用它。在C,C ++或Java等语言中,当您退出定义变量的范围时,局部变量无法生存。

C ++ 11具有未命名的函数,但仍然无法捕获变量并在范围内存活(它们可以复制到未命名函数的局部变量,但这不是一回事)。

答案 1 :(得分:4)

实际上,x是函数的本地,因此setfsetq只会更改局部变量x

要回答问题的第二部分,还有另一种方法来定义除let之外的局部变量:

(funcall (lambda (var1 var2...)
           ... use var1 var2 ...)-
   val1 val2 ...)

事实上,defun可以实现为

`(setf (symbol-function ,name) (lambda ,args ,@body))

虽然我检查过的所有实现都做得更多。

此外,symbol-function地方允许您这样做:

(setf (symbol-function '+) (function -))

虽然这通常是一个坏主意。

你的第二个问题

正在发生的事情是x是包含defun的范围的本地。它是全局变量。你正在做什么defun正在创建一个闭包,它“捕获”周围范围内的所有词法范围(不是用defvar / defparameter)变量创建并保持他们为了未来。如果要检查x的值,请添加另一个函数

(defun get-x () x)

let内。换句话说,x是函数所在的let的本地。您提供的情况类似于:

(let ((x 3))
    (print x)
    (let ((y 7))
        (print (list x y))
        (setf x y))
    (print x))

除了内部letdefunlambda)替换。

答案 2 :(得分:0)

在您的代码中xf函数的参数,因此是函数的本地参数。对setf的调用不会创建新变量,而只是设置现有局部变量x的值。使用setq代替setf会表现相同。