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

时间:2013-12-03 03:42:00

标签: lisp common-lisp

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

3 个答案:

答案 0 :(得分:4)

基本

  

Lisp中的范围对我来说是新的,我想我已经弄明白了,   但是让我感到困惑的一个方面是如何改变全局   函数中的变量,没有具体提及。

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

void frob( int x ) {
  x = 0;
}

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

您希望打印3,而不是0,因为x中的frobx中的bar是不同的变量。修改一个不会改变另一个的值。现在,在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)

答案 1 :(得分:0)

符号可以与makunbound解除绑定。然后符号不仅是空的,而是消失了。与变异一样,危险是共同的结构。 hyperspec:makunbound

符号的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
?

答案 2 :(得分:0)

如果你正在寻找惯用的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)

这也是合情合理的。