从OCaml中的列表创建双链接列表

从OCaml中的列表创建双链接列表,ocaml,lazy-evaluation,Ocaml,Lazy Evaluation,我经常被告知,使用OCaml中的Lazy模块,可以用Haskell这样的惰性语言完成所有可以完成的事情。为了测试这种说法,我试图编写一个函数,将常规列表转换为ocaml中的静态双链接列表 type 'a dlist = Dnil | Dnode of 'a dlist * 'a * 'a dlist 给定这种类型,我可以手动创建几个静态双链接列表: let rec l1 = Dnode (Dnil,1,l2) and l2 = Dnode (l1,2,l3) and l3

我经常被告知,使用OCaml中的
Lazy
模块,可以用Haskell这样的惰性语言完成所有可以完成的事情。为了测试这种说法,我试图编写一个函数,将常规列表转换为ocaml中的静态双链接列表

type 'a dlist = Dnil | Dnode of 'a dlist * 'a * 'a dlist
给定这种类型,我可以手动创建几个静态双链接列表:

let rec l1 = Dnode (Dnil,1,l2)
    and l2 = Dnode   (l1,2,l3)
    and l3 = Dnode   (l2,3,Dnil)
但是我想写一个类型为
'a list->'的函数,一个给定任何列表的数据列表
,它在OCaml中构建一个静态双链接列表。例如,给定的
[1;2;3]
它应该输出与上面的
l1
相当的内容

用Haskell编写的算法非常简单:

data DList a = Dnil | Dnode (DList a) a (DList a)

toDList :: [a] -> DList a
toDList l = go Dnil l
 where
  go _ [] = Dnil
  go h (x:xs) = let r = Dnode h x (go r xs) in r

但是我还没有弄清楚在哪里调用
lazy
,以便在OCaml中编译它。

您只能构建一个在编译时确定的形状的循环不变的严格数据结构。我不打算正式地定义或证明这一点,但直观地说,一旦创建了数据结构,它的形状就不会改变(因为它是不可变的)。所以你不能加入一个循环。如果你创建了这个循环的任何元素,你需要同时创建这个循环的所有其他元素,因为你不能有任何悬空的指针

type 'a dlist = Dnil | Dnode of 'a dlist Lazy.t * 'a * 'a dlist Lazy.t
let rec of_list list = match list with
    [] -> Dnil
    | x :: [] ->
        let rec single () = Dnode (lazy (single ()), x, lazy (single ()))
        in single ()
    | x :: y -> Dnode (
        lazy (
            of_list (match List.rev list with
                [] | _ :: [] -> assert false
                | x :: y -> x :: List.rev y
            )
        ),
        x,
        lazy (
            of_list (match list with
                [] | _ :: [] -> assert false
                | x :: y -> y @ x :: []
            )
        )
    )
let middle dlist = match dlist with
    Dnil -> raise (Failure "middle")
    | Dnode (_, x, _) -> x
let left dlist = match dlist with
    Dnil -> raise (Failure "left")
    | Dnode (x, _, _) -> Lazy.force x
let right dlist = match dlist with
    Dnil -> raise (Failure "right")
    | Dnode (_, _, x) -> Lazy.force x
Ocaml可以做Haskell可以做的事情,但是您必须让
惰性
模块参与进来!与Haskell不同,ML的数据结构是严格的,除非另有规定。惰性数据结构具有类型为
'A lazy.t
的片段。(在这个特定问题上,ML的输入比Haskell更精确。)惰性数据结构允许通过临时悬挂指针(当指针第一次被解引用时,其链接值会自动创建)来构建周期

拥有循环数据结构的另一种常见方法是使用可变节点。(事实上,严格编程的铁杆支持者可能会将惰性数据结构视为不太破坏引用透明度的可变数据结构的特例。)


当循环数据结构至少涉及一个可变组件、一个函数(闭包)或有时涉及模块时,它们最有用。但是编译器没有理由强制这样做-循环严格不可变的一阶数据结构只是一种特殊情况,偶尔会有用。

如果按照从右到左的顺序构建链表(与普通列表一样),那么每个节点的左元素只会在构建该节点本身之后构建。您需要通过使left元素变懒来表示这一点,这意味着“此值将在以后构造”:

一旦有了这个,就可以使用递归定义将每个节点构造为一个惰性值,该递归定义将惰性(仍然未构造)节点传递给构建下一个节点的函数调用(以便它可以访问上一个节点)。它实际上比看起来更简单:

let dlist_of_list list = 
  let rec aux prev = function
    | [] -> Dnil
    | h :: t -> let rec node = lazy (Dnode (prev, h, aux node t)) in 
                Lazy.force node
  in
  aux (Lazy.lazy_from_val Dnil) list

首先,类型是
'a数据列表
,而不是
'a数据列表
;)这是不对的。它构建了一个循环的双链接列表,而不是以null结尾的列表。那很好,但不是我想要的。但更重要的是,它并没有做到这一点,而是构建了一个懒惰的无限树,在观测上与我想要的是等价的,但它不会使用有界内存。特别是
left(right dlist)
将不是与
dlist
相同的对象。
type 'a mutable_dlist_value =
  | Dnil
  | Dnode of 'a mutable_dlist_value * 'a * 'a mutable_dlist_value
 and 'a mutable_dlist = 'a mutable_dlist_value ref
type 'a dlist = 
  | Dnil
  | Dnode of 'a dlist Lazy.t * 'a * 'a dlist
let dlist_of_list list = 
  let rec aux prev = function
    | [] -> Dnil
    | h :: t -> let rec node = lazy (Dnode (prev, h, aux node t)) in 
                Lazy.force node
  in
  aux (Lazy.lazy_from_val Dnil) list