此问题使用以下"懒惰列表" (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]
(ltake
是List::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本身是不可能的?
lcycle
的实现是尾递归的,而是根据{的定义来确定是否甚至可能来实现lcycle
的尾递归版本。 {1}}以上。因此,指出我的lazylist
很好,错过了我想要达到的目标。对不起,我在原帖中没有说清楚这一点。
lcycle
的此实施以及上述ltake
类型的定义来自here:
lazylist
答案 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