如何让自我参照绑定?

时间:2016-06-02 19:46:49

标签: common-lisp

我有let,我需要其中一个绑定来引用自己的身体。即我需要这样的东西:

(let ((my-value (make-value :some-data 2784 :next-value my-value)))
  ; ...)

此操作失败,因为定义中的my-value未绑定。我想我可以使用setf来分配值,但这对我来说不是一个好的通用解决方案(例如因为副作用)。

我该如何处理这种情况?也许有办法进行前瞻性声明?

2 个答案:

答案 0 :(得分:9)

  

我想我可以使用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)))
  ...)

......进入那个:

(let* ((x0 nil)
       (x (make-value :some-data d :next-value (lambda () x0))))
  (setf x0 x)
  ...)

但是你需要使用funcall 强制计算延迟值。像Haskell这样的惰性语言会隐藏这种行为。

如果你在Prolog写作,你会说:

make_value(X,D) :- X = value{data: D, next: X}.

这要归功于统一,其中可以用来执行一种有趣的副作用,即最多设置一次变量。 但是,这不可能直接在Common Lisp中实现(你可以在Lisp中实现统一,但如果唯一的目的是避免setf),你可能不应该这样做。

上述示例中发生的是副作用被隐藏。 我的观点是,没有任何魔法:不知何故,必须有一个副作用来建立一个对象和它自己之间的链接,即使它是不可见的。

您可以执行相同操作,并在本地执行副作用的实现上提供无副作用的功能。那是完全可以接受的。 首先,定义您的类型:

(defstruct (value (:constructor make-value%))
  some-data
  next-value)

基本构造函数名为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))

实现小心地将局部副作用包装到一个函数中,从外部的角度来看,它不会改变它的环境(注释:分配内存也是一个副作用)。它允许用户提供下一个元素,但默认情况下它将结构链接到自身。   以下是一个示例用法:

(let ((x (make-value :some-data 1234)))
  (assert (eq x (value-next-value x))))

答案 1 :(得分:4)

您应该遵循评论者的建议并创建对象然后进行修改:

(setq *print-circle* t)
(let ((x (list 1 2 3)))
  (setf (second x) x)
  x)
==> #1=(1 #1# 3)

如果你真的想发疯,可以使用读者魔术:

(let ((x '#1=(1 #1# 3)))
  x)
==> #1=(1 #1# 3)

但是,这会创建一个引用的对象,您以后修改它。

它还将创建一个唯一的对象,即如果不是新对象,即上述代码的每次调用都将返回相同的对象:

(defun unsafe ()
  '#1=(1 #1# 3))
(eq (unsafe) (unsafe))
==> T