Lisp 如何变异传递给函数并在函数内部变异的全局变量?

Lisp 如何变异传递给函数并在函数内部变异的全局变量?,lisp,common-lisp,Lisp,Common Lisp,我想知道如何从函数内部永久更改全局变量的值,而不在函数内部使用变量的名称,即: (defvar *test1* 5) (defun inctest (x) (incf x)) (inctest *test1*) ;after it runs, *test1* is still 5, not 6 根据: 如果传递给函数的对象是可变的,并且您在函数中对其进行了更改,则调用方将看到这些更改,因为调用方和被调用方都将引用同一对象 这不是我在上面做的吗?不,它更改了您提供的副本 要更改变量本身,请在函数

我想知道如何从函数内部永久更改全局变量的值,而不在函数内部使用变量的名称,即:

(defvar *test1* 5)
(defun inctest (x) (incf x))
(inctest *test1*) ;after it runs, *test1* is still 5, not 6
根据:

如果传递给函数的对象是可变的,并且您在函数中对其进行了更改,则调用方将看到这些更改,因为调用方和被调用方都将引用同一对象


这不是我在上面做的吗?

不,它更改了您提供的副本

要更改变量本身,请在函数体中使用其名称进行更改:
(incf*test1*)

编辑:如果您接受宏,请看,它是从我的粘液中滚烫出来的:

(defmacro my-incf (variable-name)  
  `(incf ,variable-name))

如果希望
inctest
成为函数,请将全局变量的名称传递给它

(defun inctest(x)(incf(符号值x)))
(inctest'*test1*)

您没有按照引号中的说明进行操作,即对传递给函数的对象进行变异。您正在变异参数
x
,即保存对象副本的函数局部变量

要做到引用中所说的,您需要一个实际上是可变的对象,而不是数字。如果您使用可变对象,例如列表,则它可以:

(defvar *test2* (list 5))
(defun inctest2 (x) (incf (car x)))
(inctest2 *test2*)
*test2* ; => (6)

在可移植公共Lisp中,没有明确的“指针”概念,所有参数都是通过值传递的。为了能够传递函数修改变量的能力,您需要提供一种访问变量的方法

如果变量是全局变量,则可以使用变量名称(a
symbol
),然后使用
symbol value
读取/写入变量:

(defvar *test1* 42)

(defun inctest (varname)
  (incf (symbol-value varname)))

(inctest '*test1*) ;; Note we're passing the NAME of the variable
(defun inctest (accessor)
  (funcall accessor (1+ (funcall accessor))))

(let ((x 42))
  (inctest (lambda (&optional (value nil value-passed))
             (if value-passed
                 (setf x value)
                 x)))
  (print x))
例如,如果变量为局部变量,则可以提供一个闭包,当不使用参数调用时,该闭包返回当前值,而使用参数调用时,该闭包将设置变量的新值:

(defvar *test1* 42)

(defun inctest (varname)
  (incf (symbol-value varname)))

(inctest '*test1*) ;; Note we're passing the NAME of the variable
(defun inctest (accessor)
  (funcall accessor (1+ (funcall accessor))))

(let ((x 42))
  (inctest (lambda (&optional (value nil value-passed))
             (if value-passed
                 (setf x value)
                 x)))
  (print x))
您还可以编写一个用于构建访问器的小助手:

(defmacro accessor (name)
  (let ((value (gensym))
        (value-passed (gensym)))
  `(lambda (&optional (,value nil ,value-passed))
     (if ,value-passed
         (setf ,name ,value)
         ,name))))
之后,代码变为

 (let ((x 42))
   (inctest (accessor x))
   (print x))

有些对象是按值传递的(实际上,只有数字iirc)。所以,除了数字,你所引用的通常都是正确的。如果您想通过引用传递数字,您需要一些容器,cons单元格将是最基本的容器。如果要经常使用,可能还需要缓存一些常用的数字。通过引用传递数字的另一种方法是使用带有指针的CFFI。从另一个角度看,数字永远都是不可变的,所以这句话在这里不适用。一切都是按值传递的。在任何情况下:
(defun foo(x)(setf x'foo))
都不会更改调用它的变量@OpenLearner的困惑在于变异变量和变异对象之间的区别。我知道你已经接受了答案,但10月21日(就在三天前)问了一个几乎相同的问题,这已经是第一个了。这之后的三次点击也是关于一个类似的主题。@Rörd“没有这样的情况:
(defun foo(x)(setf x'foo))
会改变它被调用的变量。”你的观点是正确的,但我不同意这种说法,只是因为
foo
命名的函数从来没有被变量调用过;它是用一个对象调用的。在
(foo a)
中,由于
foo
是一个函数
,因此对
a`进行求值,并将其值传递给
foo
<另一方面,code>setf
,因为它是一个宏,可以用变量调用(一般来说,还有位置),因此可以修改变量(即绑定的值)。@Rörd所有这些都说明,我理解你的观点,并且没有任何你误解这个概念的印象;我只是想澄清一下,为了其他可能遇到这种情况的人。我在[我对另一个问题的回答]中更详细地讨论了地点和广义参考(我讨论了位置和广义引用,在我看来,这是一个重复的引用。不,这里的想法是按照我提供的引文,将一个对象传递给函数,然后让函数对其进行变异。你建议的是在函数中直接命名一个全局变量,这与将其传递给t不同函数。在这种情况下,我们应该使用宏。但这取决于您的需要。从语法上看,它看起来是一个函数,可能会满足您的要求。谢谢,但对于这样一个简单的任务,宏不应该是必需的。事实上,Lars的答案显示了实现这一点的简单方法。我认为这个问题是一个骗局,但我确实喜欢这个答案。我要指出的一点是,虽然Lisp没有指针的概念,但它确实有位置的概念,并且
setf
修改存储在位置中的值。这里真正的问题是
(incf x…)的位置
modifies是词法变量
x
的绑定。从符号
*test*
可以得到一个合适的位置,即,
(符号名'*test*)
。我在中讨论了位置和广义引用。@JoshuaTaylor:
位置
不过是编译时概念,而指针是运行时概念。只能将位置传递给宏,不能传递给函数。是和否,我会说。例如,您可以执行
(let((varname'*test*)(setf(symbol name varname)新值)
甚至
(setf(aref a i j k)新值)
表明,计算新值放置位置的基本任务将在运行时进行,而不是在编译时。@JoshuaTaylor:如何在列表中存储位置?Lisp和Python都无法传递/存储对象的地址,只能传递/存储对象的名称或路径名。我知道的唯一方法是存储地址是使用闭包。在C++中,你可以有<代码> STD::MAP<代码>,它是从名字到整数地址的映射,你可以例如增加这些整数。