在没有明确提及的情况下永久变异变量



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习语吗?如果你有多个想要永久变异的变量,你是否必须为每个变量编写一个单独的函数,每个函数都明确提到一个不同的变量?

基础

Lisp中的范围界定对我来说是新的,我想我已经弄清楚了,但有一个领域让我有点困惑,那就是如何变异全球变量,但没有具体提及。

除了动态作用域变量的可用性之外,作用域与其他语言中的可用性没有太大区别。例如,在C中,如果您执行以下操作:

void frob( int x ) {
  x = 0;
}
int bar() { 
  int x = 3;
  frob( x );
  printf( "%d", x );
}

您希望看到打印的是3,而不是0,因为frob中的xbar中的x是不同的变量。修改其中一个不会更改另一个的值。现在,在C中,您可以获取变量的地址,并通过指针修改该变量。Common Lisp中没有指针,所以如果您想要一层间接层,则需要其他东西。对于词法变量,您需要为此使用闭包。不过,对于全局变量(动态范围),可以使用命名变量的符号。

变量的直接修改(词汇或动态)

引用全局变量的方式与引用Common Lisp中的任何其他变量的方式相同,只需写下它们的名称即可。您可以使用setqsetf修改它们。例如,你可以进行

(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))))

了解变量绑定(词法和动态)

(注意:这实际上只是上面C示例的翻版。)我想你明白为什么你发布的这段代码不起作用,但我想提一点,以防经验不足的人遇到这个问题。

(defun empty-it (var)
  "Remove everything from var."
  (setf var nil))

现在,如果我有*some-var*并调用(empty-it *some-var*),它就没有工作,因为变量保留了之前作用域中的内容进入功能。

在这里,任何变量在一个或另一个范围内保留或不保留其值都没有什么不寻常的意义。评估模型说,为了评估(empty-it *some-var*),系统找到empty-it的函数绑定,并取*some-var*,称之为x;并用x调用empty-it的函数值。在执行该调用时,变量var绑定到值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

小心动态重新绑定

现在,如果您只修改这些变量,并且永远不会为它们创建新的绑定,那么这一切都会很好。当您用defparameterdefvar定义一个变量时,全局声明它为special,即动态范围。用setsetf所做的修改是对变量的最新作用域绑定所做的。(当您修改词法变量时,您正在更新最内部的词法封闭绑定。)这将导致如下结果:

(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)

符号可以与makunbound解除绑定。然后这个符号不仅是空的,而且已经消失了。与突变一样,危险在于共享结构。hyperspec:生成未绑定的

符号的symbol-function值可以与fmakunbound解除绑定。hyperspec:fmakunbound

? (setf (symbol-value 'b) 42)
42
? (setf (symbol-function 'b)(lambda (x)(+ x 1)))
#<Anonymous Function #x210057DB6F>
? b
42
? (b 4)
5
? (fmakunbound 'b)
B
? b
42
? (b 4)
> Error: Undefined function B called with arguments (4) .
> ...[snipped]
> :pop
? b
42
? (makunbound 'b)
B
? b
> Error: Unbound variable: B
> ...[snipped]
> :pop
?

如果你正在寻找惯用的lisp,我认为(尽管我不是lisp方面的专家)你想要的是简单地不让你的函数来清空。它可以提供一个空值,就这样吧。所以,与其有:

(defun empty-it (var)
  (setf var nil))
; and then somewhere down the line calling:
(empty-it var)

你可以这样做:

(defun empty ()
  nil)
; and then somewhere down the line, call:
(setf var (empty))

Common lisp并不局限于(也许可以简单地说不是)一种函数式语言,但为此,你需要采取(更)函数式的方法——这意味着你的函数可能会取一个值,但它不会修改变量,它只是返回另一个值。

当然,如果你的目标是拥有"让这个东西变空"的语义表达,你可以使用一个宏:

(defmacro empty-it (var)
  `(setf ,var nil))
; and then, down the road, you can indeed call:
(empty-it var)

这也相当地道。

最新更新