我的代码正在等待炸毁潜伏的东西。使用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
答案 0 :(得分:5)
我相信以你建议的方式折叠Result
s序列的惯用方法是:
let unwindSeq<'a,'b> =
Seq.fold<Result<'a,'b>, Result<'a seq, 'b>>
(fun acc cur -> acc |> Result.bind (fun a -> cur |> Result.bind (Seq.singleton >> Seq.append a >> Ok)))
(Ok Seq.empty)
并不是说这比你当前的实现更快,它只是利用Result.bind
来完成大部分工作。我相信堆栈溢出是因为F#库中的某个递归函数,可能在Seq
模块中。我最好的证据是,首先将序列实现为List
似乎可以使其正常工作,如下例所示:
let results =
Seq.init 2000000 (fun i -> if i <= 1000000 then Result.Ok i else Error "too big")
|> Seq.toList
results
|> unwindSeq
|> printfn "%A"
但是,如果序列太大而无法在内存中实现,则在生产场景中这可能不起作用。