Lisp / Scheme中的自引用数据结构

时间:2009-03-03 03:25:06

标签: data-structures functional-programming lisp graph scheme

有没有办法在lisp或scheme中构建自引用数据结构(比如带循环的图形)?我之前从未想过它,但是由于缺乏进行破坏性修改的方法,我可以找不到任何简单的方法来制作它。这只是函数式语言的一个重要缺陷,如果是这样,那么像haskell这样的懒函数语言呢?

11 个答案:

答案 0 :(得分:14)

在Common Lisp中,您可以修改列表内容,数组内容,CLOS实例的插槽等。

Common Lisp还允许读写循环数据结构。使用

? (setf *print-circle* t)
T

; a list of two symbols: (foo bar)

? (defvar *ex1* (list 'foo 'bar))
*EX1*

; now let the first list element point to the list,
; Common Lisp prints the circular list

? (setf (first *ex1*) *ex1*)
#1=(#1# BAR)

; one can also read such a list

? '#1=(#1# BAR)
#1=(#1# BAR)

; What is the first element? The list itself

? (first '#1=(#1# BAR))
#1=(#1# BAR)
? 

所谓的功能编程语言不允许副作用。大多数Lisp方言纯。它们允许副作用,并允许修改数据结构。

有关详细信息,请参阅Lisp介绍书。

答案 1 :(得分:9)

在Scheme中,您可以使用set!set-car!set-cdr!(以及以爆炸('!')结尾的任何其他内容(表示修改)轻松完成此操作:

(let ((x '(1 2 3)))
  (set-car! x x)
  ; x is now the list (x 2 3), with the first element referring to itself
  )

答案 2 :(得分:9)

Common Lisp支持使用setf修改数据结构。

您可以通过tying the knot在Haskell中构建循环数据结构。

答案 3 :(得分:4)

  1. 您不需要“破坏性修改”来构建自引用数据结构;例如,在Common Lisp中,'#1=(#1#)是一个包含自身的cons-cell。

  2. Scheme和Lisp能够进行破坏性修改:您可以像上面这样构建循环缺点: (let ((x (cons nil nil))) (rplaca x x) x)

  3. 在学习Lisp / Scheme时,您能告诉我们您正在使用的材料吗?我正在编制黑色直升机的目标清单;关于Lisp和Scheme的错误信息的传播必须停止。

答案 4 :(得分:3)

是的,它们可能很有用。我的一位大学教授创建了一种名为Medusa Numbers的Scheme类型。它们是任意精度浮点数,可能包括重复小数。他有一个功能:

(create-medusa numerator denominator) ; or some such

创造了代表理性的美杜莎数字。结果:

(define one-third (create-medusa 1 3))
one-third => ; scheme hangs - when you look at a medusa number you turn to stone
(add-medusa one-third (add-medusa one-third one-third)) => 1
如前所述,这是通过明智的应用集合车来完成的!和set-cdr!

答案 5 :(得分:3)

它不仅可行,它还是Common Lisp对象系统的核心:标准类是它自己的一个实例!

答案 6 :(得分:3)

我赞成了明显的Scheme技术;这个答案只涉及Haskell。

在Haskell中,您可以使用let完全在功能上执行此操作,这被认为是好的风格。一个很好的例子是regexp到NFA的转换。您也可以使用IORef命令性地执行此操作,这被认为是糟糕的风格,因为它会强制所有代码进入IO monad。

一般来说,Haskell的懒惰评估适用于循环和无限数据结构的可爱功能实现。在任何复杂的let绑定中,所有绑定的东西都可以用在所有定义中。例如,将一个特定的有限状态机转换为Haskell非常简单,无论它有多少个循环。

答案 7 :(得分:1)

CLOS示例:

(defclass node ()
  ((child :accessor node-child :initarg :child)))

(defun make-node-cycle ()
  (let* ((node1 (make-instance 'node))
         (node2 (make-instance 'node :child node1)))
     (setf (node-child node1) node2)))

答案 8 :(得分:1)

StackOverflow上的

Tying the Knot (circular data structures in Haskell)

另见Haskell Wiki页面:Tying the Knot

答案 9 :(得分:0)

嗯,Lisp / Scheme中的自引用数据结构和SICP​​流没有提到?好吧,总结一下,流= =懒惰评估列表。它可能正是你想要的那种自我引用,但它是一种自我引用。

因此,SICP中的cons-stream是一种延迟评估其参数的语法。 (cons-stream a b)会在不评估a或b的情况下立即返回,并且仅在您调用car-streamcdr-stream

时评估a或b

来自SICP,http://mitpress.mit.edu/sicp/full-text/sicp/book/node71.html: >

(define fibs
  (cons-stream 0
               (cons-stream 1
                            (add-streams (stream-cdr fibs)
                                         fibs))))
     

这个定义说纤维是一种   以0和1开头的流,例如   其余的流可以   通过向自身添加纤维来生成   转移一个地方:

在这种情况下,'fibs'被赋予一个对象,其值根据'fibs'懒惰地定义

几乎忘了提及,懒惰的流存在于常用的SRFI-40或SRFI-41库中。其中一个应该在大多数流行的方案中都可用,我认为

答案 10 :(得分:0)

在搜索“CIRCULAR LISTS LISP SCHEME”时,我偶然发现了这个问题。

这是我如何制作一个(在STk方案中):

首先,列一个清单

(define a '(1 2 3))

此时,STk认为a是一个列表。

(list? a)
> #t

接下来,转到最后一个元素(在这种情况下为3)并将当前包含cdr的{​​{1}}替换为指向自身的指针。

nil

现在,STk认为a不是列表。

(set-cdr! (cdr ( cdr a)) a)

(它如何解决这个问题?)

现在,如果您打印(list? a) > #f ,您将找到一个无限长的a列表,您将需要终止该程序。在Stk中,您可以(1 2 3 1 2 3 1 2 ...control-z退出。

但循环列表有什么用呢?

我可以想到使用模运算的模糊示例,例如星期几control-\的循环列表,或者由3位(M T W T F S S M T W ...)表示的整数循环列表。

有没有真实的例子?