这个F#seq表达式对我来说是尾递归的,但是我得到了堆栈溢出异常(启用了尾调用)。有人知道我错过了什么吗?
let buildSecondLevelExpressions expressions =
let initialState = vector expressions |> randomize
let rec allSeq state = seq {
for partial in state do
if count partial = 1
then yield Seq.head partial
if count partial > 1 || (count partial = 1 && depth (Seq.head partial) <= MAX_DEPTH) then
let allUns = partial
|> pick false 1
|> Seq.collect (fun (el, rr) -> (createExpUnaries el |> Seq.map (fun bn -> add rr bn)))
let allBins = partial // Careful: this case alone produces result recursivley only if |numbers| is even (rightly!).
|> pick false 2
|> Seq.collect (fun (el, rr) -> (createExpBinaries el |> Seq.map (fun bn -> add rr bn)))
yield! allSeq (interleave allBins allUns)
}
allSeq initialState
如果您想知道,虽然它不重要,pick
用于生成序列中元素的组合,interleave
交错来自2个序列的元素。 vector
是ResizeArray
的构造函数。
答案 0 :(得分:4)
正如Gideon指出的那样,这不是尾递归的,因为你仍然有'状态'列表中的其他元素要处理。使这种尾递归并不简单,因为您需要一些应该处理的元素的队列。
以下伪代码显示了一种可能的解决方案。我添加了work
参数,用于存储要完成的剩余工作。在每次调用时,我们只处理第一个元素。所有其他元素都将添加到队列中。完成后,我们从队列中选择更多工作:
let rec allSeq state work = seq {
match state with
| partial::rest ->
// Yield single thing to the result - this is fine
if count partial = 1 then yield Seq.head partial
// Check if we need to make more recursive calls...
if count partial > 1 || (* ... *) then
let allUns, allBins = // ...
// Tail-recursive call to process the current state. We add 'rest' to
// the collected work to be done after the current state is processed
yield! allSeq (interleave allBins allUns) (rest :: work)
else
// No more processing for current state - let's take remaining
// work from the 'work' list and run it (tail-recursively)
match work with
| state::rest -> yield! allSeq state rest
| [] -> () //completed
| _ ->
// This is the same thing as in the 'else' clause above.
// You could use clever pattern matching to handle both cases at once
match work with
| state::rest -> yield! allSeq state rest
| [] -> () } //completed
答案 1 :(得分:3)
我找不到序列表达式中哪些调用在F#中处于尾部位置的定义,因此我强烈建议不要编写依赖于当前实现的语义的代码,即这是未定义的行为。
例如,尝试枚举(例如应用Seq.length
)以下序列会导致堆栈溢出:
let rec xs() = seq { yield! xs() }
但是,正如Tomas指出的那样,以下确实有效:
let rec xs n = seq { yield n; yield! xs(n+1) }
我的建议是始终用Seq.unfold
替换递归序列表达式。在这种情况下,您可能希望累积要完成的工作(例如,当您递归到左侧分支时,将右侧分支推入累加器中的堆栈)。
FWIW,即使the F# language reference也错了。它提供了以下用于展平树的代码:
type Tree<'a> =
| Tree of 'a * Tree<'a> * Tree<'a>
| Leaf of 'a
let rec inorder tree =
seq {
match tree with
| Tree(x, left, right) ->
yield! inorder left
yield x
yield! inorder right
| Leaf x -> yield x
}
当他们在左边输入一棵深树时,他们自己的代码会使用堆栈溢出来杀死F#interactive。
答案 2 :(得分:1)
这不会是尾递归的,因为你可以多次递归调用。要翻译成伪代码:
allSeq(state)
{
foreach (partial in state)
{
if (...)
{
yield ...
}
if (...)
{
...
//this could be reached multiple times
yield! allSeq(...)
}
}
}