F# 组合多个函数时堆栈溢出
下面是一种展平二叉搜索树的方法。它的问题是,当它构建的大型函数最终应用于[]时,堆栈溢出。我想知道是否有一种合理的方法可以在不完全改变其工作方式的情况下修复此代码片段。例如,如果构建一个自定义编写器来构建一个函数树,然后使用显式堆栈对其求值(因为问题已经是展平一棵树),这将无助于取得进展F# 组合多个函数时堆栈溢出,f#,stack-overflow,tail-recursion,continuations,F#,Stack Overflow,Tail Recursion,Continuations,下面是一种展平二叉搜索树的方法。它的问题是,当它构建的大型函数最终应用于[]时,堆栈溢出。我想知道是否有一种合理的方法可以在不完全改变其工作方式的情况下修复此代码片段。例如,如果构建一个自定义编写器来构建一个函数树,然后使用显式堆栈对其求值(因为问题已经是展平一棵树),这将无助于取得进展 树展平函数不是尾部递归函数 函数的组合不是尾部递归的。这很容易通过展开您的三重组合看到: original: fl << cons << fr unfold c
树展平函数不是尾部递归函数 函数的组合不是尾部递归的。这很容易通过展开您的三重组合看到:
original: fl << cons << fr
unfold compositions: fun a -> fl (cons (fr a))
unfold nested calls: fun a ->
let x = fr a
let y = cons x
fl y
原件:fl
设x=fr a
设y=x
弗利
如您所见,此函数首先调用fr
,然后对其结果执行一些非常重要的操作。最后一个对fl
的调用是尾部递归的,但前两个不是。当执行fr
和cons
时,返回地址需要保留在堆栈上,这是无法避免的
这不是尾部递归。尾部递归的工作原理是将最后一次调用的结果传递给堆栈上的调用方。将这个结果作为参数传递给另一个函数——这是完全不同的事情
至于如何修复它——如果你坚持使用函数组合,你就不能。如果你不坚持,那么你已经有了解决办法
就你设计的例子而言-我认为它失败了,因为你在FSI或类似的东西中运行它。我刚才已经核实了:
- 如果您正常编译它,它工作得很好
- 如果关闭优化,则会导致堆栈溢出
- 如果将
替换为一些非尾部递归函数(例如id
),它也会失败funx->x+1
let test_composition () =
let mutable f = id
for i=0 to 1000000 do
f <- id << f // >> works fine for me
printf "Functions return %d" (f 123)
let flatten t =
let rec f t acc cont =
match t with
| Leaf ->
cont acc
| Bin (l, x, r) ->
f r acc (fun rs -> f l (x::rs) cont)
f t [] id
original: fl << cons << fr
unfold compositions: fun a -> fl (cons (fr a))
unfold nested calls: fun a ->
let x = fr a
let y = cons x
fl y