Lisp 中的作用域对我来说是新的,我想我已经弄清楚了,但是让我有点困惑的一个领域是如何在不具体提及的情况下改变函数中的全局变量:
(defun empty-it (var)
"Remove everything from var."
(setf var nil))
现在,如果我有 *some-var*
并调用 (empty-it *some-var*)
它不起作用,因为变量保留其进入函数之前范围内的内容。显然,这是有效的:
(defun empty-it-explicit ()
"Remove everything *some-var*."
(setf *some-var* nil))
是否有可能有一个通用函数来清除存储变量的永久内容,并使其对传递给它的任何变量起作用?换句话说,如果您希望永久更改变量的名称,是否必须始终显式提及该变量的名称?
(defun set-somevar-with-function (fn)
"Pass *some-var* into a function and set it to the results."
(setf *some-var* (funcall fn *some-var*)))
CL> (set-somevar-with-function #'empty-it)
这是正确的 Lisp 习惯用法吗?如果您有多个想要永久变异的变量,是否必须为每个变量编写一个单独的函数,每个变量都明确提及不同的变量?
最佳答案
基础知识
The scoping in Lisp is new to me and I think I've got it figured out, but the one area that confuses me a bit is how to mutate a global variable in a function without mentioning it specifically.
除了动态范围变量的可用性之外,范围界定与其他语言中可用的范围并没有太大不同。例如,在 C 语言中,如果您执行以下操作:
void frob( int x ) {
x = 0;
}
int bar() {
int x = 3;
frob( x );
printf( "%d", x );
}
您希望看到打印 3
,而不是 0
,因为 frob
中的 x
和bar
中的 x
是不同的变量。修改一个不会改变另一个的值。现在,在 C 中,您可以获取变量的地址,并通过指针修改该变量。在 Common Lisp 中你没有指针,所以如果你想要一个间接层,你需要其他的东西。对于词法变量,您需要使用闭包来实现此目的。不过,对于全局变量(动态范围),您可以使用命名变量的符号。
直接修改变量(词法或动态)
您可以像引用 Common Lisp 中的任何其他变量一样引用全局变量,只需写下它们的名称即可。您可以使用 setq
修改它们或setf
。例如,你可以这样做
(setq *x* 'new-value-of-x)
(setf *x* 'newer-value-of-x)
间接修改变量(仅限动态)
您还可以使用符号 *x*
,并使用 set
或(setf symbol-value)
更改值:
(setf (symbol-value '*x*) 'newest-value-of-x)
(set '*x* 'newester-value-of-x)
这些情况为您提供了一些灵活性,因为它们意味着您可以将符号作为参数,因此您可以这样做:
(defun set-somevar-with-function (var-name)
(setf (symbol-value var-name)
(funcall fn (symbol-value var-name))))
理解变量绑定(bind)(词法和动态)
(注意:这实际上只是上面 C 示例的重新哈希。) 我想您明白为什么您发布的这段代码不起作用,但我想提一下以防万一经验不足的人遇到这个问题。
(defun empty-it (var) "Remove everything from var." (setf var nil))
Now, if I have
*some-var*
and call(empty-it *some-var*)
it doesn't work, because the variables retains its contents from the scope prior to entering the function.
任何变量在一个作用域或另一个作用域中保留或不保留其值并没有什么不寻常的意义。评估模型表示,要评估 (empty-it *some-var*)
,系统会找到 empty-it
的函数绑定(bind)并获取值<*some-var*
的/em>,我们称之为 x,并用 调用 empty-it
的函数值x。在执行该调用时,变量 var
绑定(bind)到值 x。调用(setf var nil)
修改变量var
,而var
与变量*some-var*无关
,只不过有一段时间它们碰巧具有相同的值。这里本质上不依赖于 *some-var*
是全局变量还是动态变量,或者依赖于 *some-var*
和 var
具有不同的名称。使用同名的另一个变量会得到相同的结果,例如:
(defun empty-it (var)
(setf var nil))
(let ((var 'value))
(empty-it var)
var)
;=> 'value
如果empty-it
的参数被称为*some-var*
,你甚至会得到相同的结果:
(defun empty-it (*some-var*)
(setf *some-var* nil))
(progn
(setf *some-var* 'value)
(empty-it *some-var*)
*some-var*)
;=> 'value
谨防动态重新绑定(bind)
现在,如果您仅修改这些变量,并且从不为它们创建新的绑定(bind),那么这一切都会正常工作。当您使用 defparameter
或 defvar
定义变量时,您也在全局声明它特殊
,即动态地声明它范围。使用 set
或 setf
完成的修改是针对变量的最新范围绑定(bind)完成的。 (当您修改词法变量时,您正在更新最里面的词法封闭绑定(bind)。)这会导致如下结果:
(defparameter *x* 'first-value) ; AA
(defun call-and-modify (function name)
(setf (symbol-value name)
(funcall function
(symbol-value name))))
(progn
(let ((*x* 'second-value)) ; BB
(let ((*x* 'third-value)) ; CC
(print *x*) ; third-value (binding CC)
(call-and-modify (constantly 'fourth-value) '*x*)
(print *x*)) ; fourth-value (binding CC)
(print *x*)) ; second-value (binding BB)
(print *x*)) ; first-value (binding AA)
关于lisp - 在没有明确提及的情况下永久改变变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20342465/