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

У меня есть 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?

4 ответа

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

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

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

Эта функция предоставляется упаковка. Функция 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
  }

(Отмечая, что других ответов здесь более чем достаточно!)

Если работать конкретно с Lists, это можно сделать с помощью sequenceфункция ( Result<'c, 'd> list -> Result<'c list, 'd>), выставленные Resultмодуль Fsharpx.Extrasбиблиотека ( исходный код).

Однако для более общих последовательностей это можно сделать с помощью Seq.sequenceResultMфункция, предоставляемая FsToolkit.ErrorHandlingбиблиотека.

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

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
Другие вопросы по тегам