LISP 局部/全局变量赋值



如果我们定义一个函数,如下所示

(defun foo(x)
  (setf x somevalue))

x定义为局部变量还是全局变量?使用 setf/q 将值设置为全局变量。如果它是全局的,谁能告诉我如何在 Lisp 中定义除 let 之外的局部变量?

谢谢!

考虑以下示例

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

当我给像(foo 2)这样的foo一些输入时,它返回 2,如果我们再次使用 (foo 1) 执行该函数,它仍然返回 2,(foo 3)返回 3。但问题是这怎么可能,因为如果我尝试从 clisp 终端访问函数外部的变量x,我无法这样做。如果我再次访问该函数,它似乎保留了以前的 x 值。

谢谢!

为了与其他语言(如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后恢复值。

所以这些变量既是"全局的"(因为外部函数可以看到它们(,也是局部的(因为在你的函数或 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 具有未命名的函数,但仍然无法捕获变量并在作用域中幸存下来(它们可以复制到未命名函数的局部变量中,但这不是一回事(。

实际上,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))

除了内部letdefun(这是一个lambda(取代。

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

最新更新