如何在lisp中全局更改函数内的变量值

smi*_*ael 2 lisp common-lisp argument-passing

我想知道是否有任何方法可以使用LISP中的指针模拟C行为.在C中如果更改变量的值,该指针指向,则它具有全局效果(即该值也将在函数外部更改).

所以,如果我有

(defun mutate ( a ) 
   (some-magic-function a 5)
)
Run Code Online (Sandbox Code Playgroud)

无论以前是什么,a都会在调用mutate后转为5.

我知道带有列表的元素有可能(很多是副作用) 在common-lisp中,如何在不更改原始列表的情况下从函数中修改list参数的一部分? 但我想知道如何为整个清单做这件事.

Jos*_*lor 8

讨论C代码

sds的答案解决了问题的要点,但看起来对于你正在模拟的C代码中发生的事情有点混淆:

我想知道是否有任何方法可以使用LISP中的指针模拟C行为.在C中如果更改变量的值,该指针指向,则它具有全局效果(即该值也将在函数外部更改).

请考虑以下内容,我认为这与您提供的Lisp代码非常相似:

#include<stdio.h>

int a = 3;

int mutate( int a ) {
  return a = 5;
}

int main() { 
  mutate( a );         /* or mutate( 8 ) or anything other argument */
  printf( "%d\n", a ); /* prints 3 */
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

代码打印三个,因为ain mutate是一个仅存在于其中的变量mutate.仅仅因为它与全球共享一个名称a并不意味着改变一个将改变另一个.此代码中的唯一的地方,你可以改变的值mutate的变量amutate.您没有选择"更改[a]指针所指向的变量的值".你可以做的是将指针传递给变量的值,通过该指针修改值,然后在值中观察结果.这将对应于此C代码:

#include<stdio.h>

int a = 3;

int mutate( int *a ) {
  return (*a = 5);
}

int main() { 
  mutate( &a );
  printf( "%d\n", a ); /* prints 5 */
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

间接通过结构

您也可以使用您喜欢的任何间接方式在Common Lisp中执行此类操作.举例来说,如果你让a一个cons细胞,其car3,那么你可以传递cons周围和修改它的价值car:

CL-USER> (defparameter *a* (cons 3 nil))
*A*
CL-USER> (defun mutate (cons)
           (setf (car cons) 5))
MUTATE
CL-USER> (mutate *a*)
5
CL-USER> (car *a*)
5
Run Code Online (Sandbox Code Playgroud)

但是,你没有在Lisp中使用address-of运算符,所以你不能完全模拟C代码,如果你想使用这种方法,你总是需要以某种方式"包装"这个值. .您可以使用Common Lisp中的现有结构,例如cons单元格,向量或您可以找到的任何其他内容.

广义参考文献

虽然它没有C风格的指针,但Common Lisp定义了一种非常宽泛的方式来引用读写内存位置,称为通用引用.

5.1.1地点概述和广义参考

通用引用是使用表单(有时称为地点),就好像它是可以读写的变量一样.地点的值是地点表单评估的对象.可以使用setf更改地点的值.绑定场所的概念未在Common Lisp中定义,但允许实现通过定义此概念来扩展语言.

在Common Lisp中,您可以使用分配给场所setf.在该SDS了建议份额的共同特点是,你可以修改全局变量的值或者通过使用全局变量符号作为一个地方setf,或者用symbol-value.也就是说,一个定义,如后 (defparameter *a* 3)两者*a*(symbol-value '*a*)地方您可以在其中存储的新值*a*.其结果是,我宁愿写变量名的宏placevalue,所以很显然,任何地方都可以被用来作为一个参数:

(defmacro mutate (place value)
  `(setf ,place ,value))
Run Code Online (Sandbox Code Playgroud)

使用词法闭包模拟C变量指向变量的指针

因为词汇变量也是地方,所以还有另一种选择尚未被考虑.您可以使用词法闭包创建函数,这些函数将提供与C样式指针相同的功能.

(defmacro make-pointer (place)
  `(lambda (op &optional value)
     (ecase op
       ((read)  ,place)
       ((write) (setf ,place value)))))

(let* ((x 3)
       (xp (make-pointer x)))
  (funcall xp 'write 5)             ; write a new value to x
  (list (funcall xp 'read)          ; read the value from x through xp
        x))                         ; read the value from x directly
;=> (5 5)
Run Code Online (Sandbox Code Playgroud)

在此代码中,make-pointer返回一个可以使用一个或两个参数调用的函数.第一个参数应该是一个符号,无论是readwrite,和第二个参数,它应该提供当第一是write,在地方来存储一个新值.调用时read,返回该位置的值.调用时write,会存储并返回一个新值.

但是,这里有一些多重评估的问题.例如,如果您要执行以下操作,请记住(print 2)返回值2:

(make-pointer (aref some-array (print 2)))
Run Code Online (Sandbox Code Playgroud)

2 每次使用指针读取或写入都会打印,这可能是不希望的.我不知道这个问题是否需要解决,但请继续阅读一些可能的方法来避免这个问题.

在对类似问题进行了一些研究之后(如何改变传递给函数并在函数内部变异的全局变量?),值得注意的是,Lisp机器(运行Lisp Machine Lisp,而不是Common Lisp)有一个更像C指针的概念叫做locatives,在Common Lisp的答案中简要提到 ,引用了值和实际值.一旦你知道要搜索的术语,就可以很容易地找到更多有关定位的内容,包括第13章 Lisp机器手册中的定位和Common Lisp的各种重新实现,包括Alan Crowe的开头是一个以a结尾的长评论(很有希望) )简明摘要:

;;; The basic idea is to use closures
Run Code Online (Sandbox Code Playgroud)

后来(源代码读得非常好),你会得到:

;;; It looks as though we are done
;;; now we can translate C code
;;; &x = (addr x), *x = (data x)
Run Code Online (Sandbox Code Playgroud)

但有一个警告

;;; The trouble is, we have a multiple evaluation bug.
Run Code Online (Sandbox Code Playgroud)

Crowe继续展示如何get-setf-expansion使用如何创建记住如何访问位置并将值存储到其中的函数,而无需(print 2)每次都进行评估.该代码当然值得一读!