Stack_overflow使用OCaml处理List

时间:2014-05-14 16:23:59

标签: ocaml stack-overflow tail-recursion

所以基本上我在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

所以我的问题是:

  1. 我可以延长堆栈的长度并避免此错误吗?

  2. 我知道tail recursive可以在这种情况下有所帮助,但我是否必须重新编写代码才能启用tail-recursive?这需要大量手动修改...... OCaml的编译器可以帮助解决这个问题吗?

2 个答案:

答案 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转换:您必须自己将非尾部调用转换为尾部调用。