我有一个let,我需要一个绑定在身体中引用自己.即我需要这样的事情:
(let ((my-value (make-value :some-data 2784 :next-value my-value)))
; ...)
Run Code Online (Sandbox Code Playgroud)
这会失败,因为my-value定义是未绑定的.我想我可以用它setf来分配值,但对我来说这不是一个好的通用解决方案(例如因为副作用).
我该如何处理这种情况?也许有办法进行前瞻性声明?
我想我可以使用setf来分配值,但这对我来说不是一个好的通用解决方案(例如,因为副作用).
有些语言旨在支持自引用变量,例如代数系统,您可以编写x = x * 4 - 2,系统可以解决您的等式x.在Common Lisp中,评估规则表明您必须能够x在make-value能够为其分配有意义的值之前在参数中进行评估x.
你可以延迟操作lambda.例如,您可以添加一个lazy-let宏来转换它:
(lazy-let ((x (make-value :some-data d :next-value x)))
...)
Run Code Online (Sandbox Code Playgroud)
......进入那个:
(let* ((x0 nil)
(x (make-value :some-data d :next-value (lambda () x0))))
(setf x0 x)
...)
Run Code Online (Sandbox Code Playgroud)
但是你需要强制计算延迟值,用a funcall.像Haskell这样的惰性语言会隐藏这种行为.
如果你在Prolog写作,你会说:
make_value(X,D) :- X = value{data: D, next: X}.
Run Code Online (Sandbox Code Playgroud)
这可以归功于统一,其中可以用来执行一种有趣的副作用,即最多设置一次变量.但是,这在Common Lisp中是不可能的(你可以在Lisp中实现统一,但如果唯一的目的是避免,你可能不应该这样做setf).
在上面的例子中发生的是隐藏了副作用.我的观点是,没有任何魔法:不知何故,必须有一个副作用来建立一个对象和它自己之间的链接,即使它是不可见的.
您可以执行相同操作,并在本地执行副作用的实现上提供无副作用的功能.那是完全可以接受的.首先,定义您的类型:
(defstruct (value (:constructor make-value%))
some-data
next-value)
Run Code Online (Sandbox Code Playgroud)
命名基本构造函数make-value%,该名称可能不会由您的包导出.然后,您定义面向用户的构造函数:
(defun make-value (&key some-data (next nil nextp))
(let ((value (make-value% :some-data some-data)))
(setf (value-next-value value) (if nextp next value))
value))
Run Code Online (Sandbox Code Playgroud)
实现小心地将局部副作用包装到一个函数中,从外部的角度来看,它不会改变它的环境(注释:分配内存也是一个副作用).它允许用户提供下一个元素,但默认情况下它将结构链接到自身.以下是一个示例用法:
(let ((x (make-value :some-data 1234)))
(assert (eq x (value-next-value x))))
Run Code Online (Sandbox Code Playgroud)