在计算表达式中,如何将结果列表转换为列表结果?

15
我有一个>列表,我希望按照以下规则将其转换为单个>:
  • 如果任何一个是,则结果应为
  • 如果结果是,则它应该是列表中的第一个
  • 如果每个结果都是,则结果应为,并且应保持列表顺序
所以我尝试实现了这个:
let all xs = 
  let folder = fun state next -> 
    match (state, next) with 
    | (Result.Ok ys, Result.Ok y) -> ys |> List.append [ y ] |> Result.Ok
    | (Result.Error e, _) -> Result.Error e 
    | (_, Result.Error e) -> Result.Error e 
  Seq.fold folder (Result.Ok []) xs

然而,这似乎是标准库中可能已经实现的东西。它有吗?

其次,我有一个类似于以下形式的Result计算表达式:

type ResultBuilder () = 
  member this.Bind(x, f) = 
    match x with 
    | Result.Ok o -> f o
    | Result.Error e -> Result.Error e
  member this.Return(value) = Result.Ok value
  member this.ReturnFrom(value) = value

let result = new ResultBuilder()

我可以在result { ... }中使用all,但是否可以进行更进一步的集成呢?例如通过实现ResultBuilder.For


它不在FSharpCore中,但一些扩展库已经实现了该功能。例如,在F#+中,它被称为sequence,可适用于任何可遍历对象 - Gus
4个回答

10
你有一个 Result<'a, 'e> list,想要得到一个 Result<'a list, 'e>。这听起来像是在https://fsharpforfunandprofit.com/posts/elevated-world-4/中描述的 sequence 函数(尽管名称听起来与seq无关)。对https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/result.fs进行快速检查表明,标准FSharp.Core库中尚未实现此函数,因此您需要自己实现它。

顺便说一句,如果您还没有阅读过Scott Wlaschin的“Elevated World”系列文章,我不建议您从我链接的这篇文章开始阅读。相反,请从这篇文章开始,因为它构建了理解“traverse”和“sequence”函数所需的背景知识。然后您就会知道实现其中一个函数的一般模式。

关于您的第二个问题,能否提供更具体的细节?比如说,您想从ResultBuilder.For获得什么行为?期望for表达式的正常行为是将Result<'a, 'e> list(或序列或数组)作为输入,并且对于列表、序列或者数组中的每个Result<'a, 'e>运行一次内部块。如果您试图在这里使用all函数,则会出现类型不匹配的错误,因为F#期望CE的.For方法产生的是Result<'a, 'e>类型,而您的all方法返回的是Result<'a list, 'e>。那么您具体希望您的ResultBuilder.For方法做些什么呢?

抱歉回复晚了。在Haskell中,序列的相应概念是什么?我读了一些Haskell,足以理解bind是单子,map是函子,apply是可应用函子。但是我还无法将序列与Haskell中的任何内容联系起来。谢谢! - Tri

8
这个功能由 FsToolkit.ErrorHandling 包提供。函数 List.sequenceResultM 将返回以下之一:
  • 一个 Result.Ok,其中包含所有 Ok 值的列表
  • 或者,一个只包含第一个 Error 值的 Result.Error
还有一个变体 List.sequenceResultA,它返回找到的所有错误的列表。
#r "nuget: FsToolkit.ErrorHandling, 2.0.0"

open FsToolkit.ErrorHandling

let xs : Result<int, string> list =
  [
    Ok 123
    Ok 456
  ]

let xa = List.sequenceResultA xs // Ok [123; 456]
let xm = List.sequenceResultM xs // Ok [123; 456]

printfn "xa: %A" xa
printfn "xm: %A" xm

let ys =
  [
    Ok 123
    Ok 456
    Error "abc"
    Ok 789
  ]

let ya = List.sequenceResultA ys // Error ["abc"]
let ym = List.sequenceResultM ys // Error "abc"

printfn "ya: %A" ya
printfn "ym: %A" ym

let zs =
  [
    Ok 123
    Error "abc"
    Error "def"
    Ok 456
  ]

let za = List.sequenceResultA zs // Error ["abc"; "def"]
let zm = List.sequenceResultM zs // Error "abc"

printfn "za: %A" za
printfn "zm: %A" zm

xa: Ok [123; 456]
xm: Ok [123; 456]
ya: Error ["abc"]
ym: Error "abc"
za: Error ["abc"; "def"]
zm: Error "abc"

在计算表达式方面(也由 FsToolkit.ErrorHandling 提供),你可以这样做:

#r "nuget: FsToolkit.ErrorHandling, 2.0.0"

open FsToolkit.ErrorHandling

let printAll xs =
  result {
    let! xs = List.sequenceResultA xs

    for x in xs do
      printfn "%A" x
  }

1
(注意这里的其他答案已经足够!)
如果专门使用List,可以使用Fsharpx.Extras库的Result模块公开的sequence函数(Result<'c,'d> list-> Result<'c list,'d>)来完成此操作(源代码请参见source)。
但是,对于更一般的序列,可以使用FsToolkit.ErrorHandling库提供的Seq.sequenceResultM函数来完成此操作。

0

这是我的解决方案,它接受一个结果序列(不是列表),因此验证是延迟的。

let takeTo<'T> predicate (source: 'T seq) =
    seq {
        use en = source.GetEnumerator()
        let mutable isDone = false

        while isDone = false && en.MoveNext() do
            yield en.Current
            isDone <- predicate en.Current
    }

let fromResults rs = 
    rs
    |> Seq.scan (fun (vs, err) i ->
        match i with
        | Ok v -> (v::vs,err)
        | Error e -> (vs, Some e)) ([], None)
    |> Seq.takeTo (fun (vs, err) -> err.IsSome)
    |> Seq.last
    |> fun (vs, err) ->
        match err with
        | None -> vs |> List.rev |> Ok
        | Some err -> Error err

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接