Превратить список результатов в результат списка внутри выражения вычисления?

У меня есть Result<'T, 'E> list, который я хотел бы превратить в сингл Result<'T list, 'E>, следуя этим правилам:

  • Если какой-либо Result является Error, тогда результат должен быть Error
  • Если результат - Error, он должен быть первым Error в списке.
  • Если каждый результат - OK, тогда результат должен быть Ok и порядок в списке должен быть сохранен.

Итак, я решил реализовать это следующим образом:

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()

Я могу использовать all внутри result { ... }, но возможна ли дальнейшая интеграция? Например, реализовав ResultBuilder.For?


person sdgfsdh    schedule 10.06.2018    source источник
comment
Этого нет в FSharpCore, но в некоторых библиотеках расширений эта функция реализована, например, в F # + она называется sequence и работает для любого Traversable.   -  person Gus    schedule 13.06.2018


Ответы (3)


У вас есть Result<'a, 'e> list, и вы хотите Result<'a list, 'e>. Это похоже на функцию sequence, описанную в https://fsharpforfunandprofit.com/posts/elevated-world-4/ (который не имеет ничего общего с seqs, несмотря на то, как звучит название). Быстрая проверка https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/result.fs предполагает, что эта функция не была реализована в стандартной библиотеке FSharp.Core, поэтому вам придется реализовать ее самостоятельно. .

Кстати, если вы еще не читали серию «Надземный мир» Скотта Влашина, я не рекомендую начинать с середины статьи, на которую я ссылаюсь. Вместо этого начните с этой статьи, поскольку она дает базовые знания, необходимые для понимания того, что функции "и" последовательность "делают. Тогда вы узнаете общую схему реализации одной из этих функций.

Что касается вашего второго вопроса, не могли бы вы дать еще несколько подробностей? Например, какое поведение вы хотите от ResultBuilder.For? Нормальное поведение, ожидаемое от выражения for, будет заключаться в том, чтобы взять Result<'a, 'e> list (или seq или массив) и запустить внутренний блок один раз для каждого Result<'a, 'e>, находящегося в списке, seq или массиве. Если вы попытаетесь использовать здесь свою all функцию, у вас будет несоответствие типов между Result<'a, 'e> (это то, что F # ожидает от .For метода CE) и Result<'a list, 'e>, которое возвращает ваш all метод. Что конкретно вы хотите, чтобы ваш ResultBuilder.For метод делал?

person rmunn    schedule 11.06.2018

Эта функция предоставляется пакетом FsToolkit.ErrorHandling. Функция List.sequenceResultM вернет либо:

  • Result.Ok, содержащий список всех Ok значений
  • Или Result.Error, содержащий только первое 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
let xm = List.sequenceResultM xs

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

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

let ya = List.sequenceResultA ys
let ym = List.sequenceResultM ys

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

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

let za = List.sequenceResultA zs
let zm = List.sequenceResultM zs

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
  }
person sdgfsdh    schedule 10.02.2021

Вот мое решение, которое принимает последовательность (а не список) результатов, поэтому проверка выполняется лениво.

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
person JustinM    schedule 06.10.2020