我编写了一个可以打印文件所有内容的函数
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的早期版本中一直是一个一致的问题。编写尾部递归代码以处理文件非常棘手。在最近的修订中,您可以使用exception
的match
子句来获取递归调用的尾部位置:
let rec print_file channel =
match input_line channel with
| line -> print_endline line; print_file channel
| exception End_of_file -> ()