【发布时间】:2018-06-07 19:09:25
【问题描述】:
我的代码正在等待炸毁潜伏的东西。使用 F# 4.1 Result 与此类似:
module Result =
let unwindSeq (sourceSeq: #seq<Result<_, _>>) =
sourceSeq
|> Seq.fold (fun state res ->
match state with
| Error e -> Error e
| Ok innerResult ->
match res with
| Ok suc ->
Seq.singleton suc
|> Seq.append innerResult
|> Ok
| Error e -> Error e) (Ok Seq.empty)
这里明显的瓶颈是Seq.singleton 添加到Seq.append。我知道这很慢(而且写得不好),但为什么它必须炸毁堆栈?我不认为 Seq.append 本质上是递归的......
// blows up stack, StackOverflowException
Seq.init 1000000 Result.Ok
|> Result.unwindSeq
|> printfn "%A"
顺便说一句,为了展开一系列Result,我使用简单的try-catch-reraise 修复了这个函数,但感觉也低于标准。关于如何在不强制评估序列或炸毁堆栈的情况下更惯用地执行此操作的任何想法?
不那么完美的展开(它也强制结果失败类型),但至少没有对序列进行预评估:
let unwindSeqWith throwArgument (sourceSeq: #seq<Result<_, 'a -> 'b>>) =
try
sourceSeq
|> Seq.map (throwOrReturnWith throwArgument)
|> Ok
with
| e ->
(fun _ -> raise e)
|> Error
【问题讨论】:
-
您是在 FSI 还是在调试版本中运行它?如果是这样,尾调用优化可能会被禁用。并不是说您在此代码示例中执行了任何尾递归操作,但可能需要检查。
-
@Aaron 好点。在主项目中,无论调试还是发布,它都会爆炸,上面的示例我只尝试了调试设置。让我检查一下。
-
Seq.append不是递归的,但Seq.fold是。 -
@Fyodor Soikin 我认为这取决于 F# 的版本。查看 GitHub 上的源码,
Seq.fold目前使用了 for 循环和可变累加器,但它曾经是尾递归的:github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/… -
@Aaron,我暂时离开了,但刚刚测试了一个发布版本:SOE 发生得更快,这是意料之中的,除了它是一样的。 @fyodor 我只检查了最近的
Seq库实现,因为我使用了最新的 FSharp.Core,事实上,我不认为Seq.fold或Seq.append是递归定义的。
标签: recursion f# stack-overflow