Превратить список Результата в Результат списка внутри выражения вычисления?
У меня есть 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
}
(Отмечая, что других ответов здесь более чем достаточно!)
Если работать конкретно с
List
s, это можно сделать с помощью
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