所以基本上我在Ocaml中处理一个长列表,我得到了Stack_overflow
错误。
然后我做了这个实验,错误再次发生。
let rec create l =
match l with
| 0 -> []
| _ -> "00"::(create (l-1))
let ll = create 999999; (*my list can be as long as around 100k*)
我使用ocamlbuild
将此代码构建到native
中,运行它然后代码粉碎了,我得到了这个:
Fatal error: exception Stack_overflow
所以我的问题是:
我可以延长堆栈的长度并避免此错误吗?
我知道tail recursive
可以在这种情况下有所帮助,但我是否必须重新编写代码才能启用tail-recursive
?这需要大量手动修改...... OCaml的编译器可以帮助解决这个问题吗?
答案 0 :(得分:2)
通过使递归调用成为函数的最后一个函数来实现尾递归。在您的情况下,在递归调用之后,有一个列表连接。通常的解决方法是使用累加器:
let create l =
let rec create2 l accu =
match l with
| 0 -> accu
| _ -> create2 (l-1) ("00"::accu)
in create2 l []
let ll = create 999999;;
print_int (List.length ll) (* outputs 999999 *)
答案 1 :(得分:2)
一般来说,增加堆栈大小并没有真正帮助你,因为很快就会遇到更大的例子,它们会占用你的扩大堆栈。你应该改变你的代码。在将简单的一些非tail-rec函数转换为tail-rec之后,您应该更容易从头开始编写tail-rec函数。
将非尾部调整函数转换为尾部调整的另一种方法是使用CPS转换http://en.wikipedia.org/wiki/Continuation-passing_style:
let rec create' k = function
| 0 -> k []
| l -> create' (fun xs -> k ("00"::xs)) (l-1)
let create = create (fun x -> x)
当我将复杂递归函数转换为非尾部函数时,有时我个人发现它比添加累加器更容易,但结果代码可能更难以阅读。
一些函数式语言编译器使用此CPS转换来消除非尾部调用。因此,由于非尾调用,它们没有堆栈溢出的问题。但是,OCaml基于堆栈,因此没有自动CPS转换:您必须自己将非尾部调用转换为尾部调用。