文件 OCaml 的尾递归读取

Tail recursive reading of file OCaml

我写了一个打印文件所有内容的函数

let rec print_file channel =
    try
    begin
        print_endline (input_line channel);
        print_file channel
    end
    with End_of_file -> ()

并且由于 print_file 是最后一个操作,我认为它会针对常规循环进行优化。但是当我在非常大的文件上运行我的程序时,我遇到了堆栈溢出。 所以我试图将 input_line 函数包装到 input_line_opt 中,这不会引发异常并且 print_file.

的代码几乎没有变化

let input_line_opt channel = 
    try Some (input_line channel)
    with End_of_file -> None

let rec print_file channel =
    let line = input_line_opt channel in
    match line with
        Some line -> (print_endline line; print_file channel)
        | None -> ()

现在它像常规尾递归函数一样工作并且不会溢出我的堆栈。 这两个函数有什么区别?

在第一个例子中,try ... with是在递归调用print_file之后发生的操作。所以这个函数不是尾递归的。

你可以想象try在栈上设置一些数据,with从栈中移除数据。由于你在递归调用后删除了数据,所以堆栈越来越深。

这是 OCaml 早期版本中的一个一致问题。编写用于处理文件的尾递归代码非常棘手。在最近的修订版中,您可以使用 matchexception 子句来获取递归调用的尾部位置:

let rec print_file channel =
    match input_line channel with
    | line -> print_endline line; print_file channel
    | exception End_of_file -> ()