这个序列表达式应该是尾递归的吗?
这个 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
的构造函数。
This F# seq expression looks tail-recursive to me, but I'm getting stack overflow exceptions (with tail-calls enabled). Does anybody know what I'm missing?
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
If you're wondering, though it shouldn't be important, pick
is used to generate combinations of elements in a sequence and interleave
interleaves elements from 2 sequences. vector
is a constructor for a ResizeArray
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如 Gideon 指出的那样,这不是尾递归,因为“状态”列表中还有其他元素需要处理。进行尾递归并不简单,因为您需要一些要处理的队列元素。
以下伪代码显示了一种可能的解决方案。我添加了
work
参数来存储剩余的待完成工作。在每次调用时,我们只处理第一个元素。所有其他元素都将添加到队列中。完成后,我们从队列中选择更多工作:As Gideon pointed out, this is not tail-recursive, because you still have other elements in the 'state' list to process. Making this tail-recursive isn't straightforward, because you need some queue of elements that should be processed.
The following pseudo-code shows one possible solution. I added
work
parameter that stores the remaining work to be done. At every call, we process just the first element. All other elements are added to the queue. When we finish, we pick more work from the queue:我找不到序列表达式中哪些调用位于 F# 尾部位置的定义,因此我强烈建议不要编写依赖于当前实现的语义的代码,即这是未定义的行为。
例如,尝试枚举(例如应用
Seq.length
)以下序列会导致堆栈溢出:但是,正如 Tomas 指出的,以下内容确实有效:
我的建议是始终替换递归序列表达式用
Seq.unfold
代替。在这种情况下,您可能希望累积要完成的工作(例如,当您递归到左分支时,您将右分支推入累加器中的堆栈)。FWIW,甚至F# 语言参考也会出错。它提供了以下用于展平树的代码:
当在左侧提供一棵深树时,他们自己的代码会通过堆栈溢出杀死 F# 交互。
I cannot find a definition of which calls inside a sequence expression are in tail position in F# so I would strongly recommend not writing code that depends upon the semantics of the current implementation, i.e. this is undefined behaviour.
For example, trying to enumerate (e.g. applying
Seq.length
) the following sequence causes a stack overflow:but, as Tomas pointed out, the following does actually work:
My advice is to always replace recursive sequence expressions with
Seq.unfold
instead. In this case, you probably want to accumulate the work to be done (e.g. when you recurse into a left branch you push the right branch onto the stack in the accumulator).FWIW, even the F# language reference gets this wrong. It gives the following code for flattening a tree:
Their own code kills F# interactive with a stack overflow when fed a deep tree on the left.
这不会是尾递归,因为您可能会递归调用多次。翻译成伪代码:
This is not going to be tail recursive because you could be calling recursively multiple times. To translate to a pseudo-code: