Streams(又名“懒惰列表”)和尾递归

时间:2014-11-20 16:57:09

标签: ocaml lazy-evaluation

此问题使用以下"懒惰列表" (aka" stream")输入:

type 'a lazylist = Cons of 'a * (unit -> 'a lazylist)

我的问题是:如何定义一个尾递归函数lcycle,它将非空(和非惰性)列表l作为参数,并返回lazylist对应于在元素l上重复循环。例如:

# ltake (lcycle [1; 2; 3]) 10;;
- : int list = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1]

ltakeList::take的懒惰模拟;我在本文末尾给出了一个实现。)

我已经实现了lcycles的几个非尾递归版本,例如:

let lcycle l =
  let rec inner l' =
    match l' with
    | []   -> raise (Invalid_argument "lcycle: empty list")
    | [h]  -> Cons (h, fun () -> inner l)
    | h::t -> Cons (h, fun () -> inner t)
  in inner l

...但我没有设法写一个尾递归的。

基本上,我遇到了通过表单构造实现延迟评估的问题

Cons (a, fun () -> <lazylist>)

这意味着我的所有递归调用都发生在这样的构造中,这与尾递归不兼容。

假设上面定义的lazylist类型,是否可以定义尾递归lcycle?或者OCaml本身是不可能的?

编辑:我的动机不是要修复&#34;我对lcycle的实现是尾递归的,而是根据{的定义来确定是否甚至可能来实现lcycle的尾递归版本。 {1}}以上。因此,指出我的lazylist很好,错过了我想要达到的目标。对不起,我在原帖中没有说清楚这一点。


lcycle的此实施以及上述ltake类型的定义来自here

lazylist

2 个答案:

答案 0 :(得分:1)

我不明白这个定义有多大问题。对inner的调用是在lcycle返回之前不会被调用的函数内。因此,没有堆栈安全问题。

这是另一种将空列表测试移出惰性循环的替代方法:

let lcycle = function
  | [] -> invalid_arg "lcycle: empty"
  | x::xs ->
    let rec first = Cons (x, fun () -> inner xs)
    and inner = function
      | [] -> first
      | y::ys -> Cons (y, fun () -> inner ys) in
    first

答案 1 :(得分:1)

问题是你正试图解决一个不存在的问题。 of_list函数不会占用任何堆栈空间,这就是懒惰列表如此之大的原因。让我试着解释一下这个过程。当您将of_list函数应用于非空列表时,它会创建列表头部的Cons和一个闭包,它捕获对列表尾部的引用。之后它瞬间回归。而已。所以它只占用很少的内存,而且没有一个使用堆栈。一个单词包含x值,另一个包含一个闭包,仅捕获指向xs的指针。

那么,你解构了这一对,你得到了你现在可以使用的值x,并且接下来是函数,这实际上是一个闭包,当被调用时,它将应用于一个列表,如果它是非空的,将返回另一个Cons。请注意,之前的缺点已经被破坏为垃圾,因此不会使用新的内存。

如果您不相信,可以构造一个永不终止的of_list函数(即,将在列表上循环),并使用iter函数打印它。它将永远运行,不会占用任何记忆。

type 'a lazylist = Cons of 'a * (unit -> 'a lazylist)

let of_list lst =
  let rec loop = function
    | [] -> loop lst
    | x :: xs -> Cons (x, fun () -> loop xs) in
  loop lst

let rec iter (Cons (a, next)) f =
  f a;
  iter (next ()) f