我是still试图实现2-3根手指树,我取得了很好的进展(repository)。在做一些基准测试时,我发现当树很大时,我的基本toList
结果为StackOverflowException
。起初,我看到一个简单的修复,并使其成为尾递归。我怎样才能得到这个函数是尾递归?
不幸的是,事实证明,toList
是不是罪魁祸首,但viewr
是:
/// Return both the right-most element and the remaining tree (lazily).
let rec viewr<'a> : FingerTree<'a> -> View<'a> = function
| Empty -> Nil
| Single x -> View(x, lazyval Empty)
| Deep(prefix, deeper, One x) ->
let rest = lazy (
match viewr deeper.Value with
| Nil ->
prefix |> Digit.promote
| View (node, lazyRest) ->
let suffix = node |> Node.toList |> Digit.ofList
Deep(prefix, lazyRest, suffix)
)
View(x, rest)
| Deep(prefix, deeper, Digit.SplitLast(shorter, x)) ->
View(x, lazy Deep(prefix, deeper, shorter))
| _ -> failwith Messages.patternMatchImpossible
寻找唯一的递归调用,很明显,这是不是尾递归。不知何故,我希望这个问题不存在,因为这个电话是在一个Lazy
包裹,恕我直言,类似于延续。
我听过并读过延续,但到目前为止从未(必须)使用(d)他们。我想我真的需要。我一直在盯着代码很长一段时间,把函数参数放在这里和那里,叫他们其他的地方... 我完全失去了!
这怎么办?
更新:调用代码看起来是这样的:
/// Convert a tree to a list (left to right).
let toList tree =
let rec toList acc tree =
match viewr tree with
| Nil -> acc
| View(head, Lazy tail) -> tail |> toList (head::acc)
toList [] tree
更新2:导致崩溃是这个代码。
let tree = seq {1..200000} |> ConcatDeque.ofSeq
let back = tree |> ConcatDeque.toList
树得到建好,我检查,它只有12层深。第2行的呼叫触发了溢出。
更新3:kvb是正确的,那pipe issue我跑进前有事情做与此有关。重新测试调试/发布的交叉产品以及使用/不使用管道时,它的工作情况除一种情况外:管道运营商的调试模式崩溃。 32位和64位的行为是一样的。
我很确定发布问题时我运行的是发布模式,但今天它正在运行。也许还有一些其他因素...对此抱歉。
虽然碰撞是解决了,我要离开这个问题打开了理论上的意义。毕竟,我们在这里学习,不是吗?
所以让我适应的问题:
从看代码,viewr
绝对不是尾递归。为什么它不会总是爆炸,以及如何使用延续重写它?
这有帮助吗? http://blog.ploeh.dk/2015/12/22/tail-recurse –
懒惰的确意味着根本没有递归调用 - 问题在于这个函数和它的调用者之间的交互。你还可以提供调用代码吗? – kvb
@kvb:完成。如果您需要查看更多信息,则整个回购在开始时会链接。 – primfaktor