Skip to content

Commit 2c6e897

Browse files
committed
feat(Seq.traverse/sequence*)!: Yield arrays
1 parent 7ae7bf5 commit 2c6e897

File tree

1 file changed

+65
-52
lines changed
  • src/FsToolkit.ErrorHandling

1 file changed

+65
-52
lines changed

src/FsToolkit.ErrorHandling/Seq.fs

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,44 @@ module FsToolkit.ErrorHandling.Seq
1010
/// <param name="state">The initial state</param>
1111
/// <param name="f">The function to apply to each element</param>
1212
/// <param name="xs">The input sequence</param>
13-
/// <returns>A result with the ok elements in a sequence or the first error occurring in the sequence</returns>
13+
/// <returns>A result with the ok elements in an array or the first error occurring in the sequence</returns>
1414
let inline traverseResultM'
15-
state
15+
(state: Result<'okOutput seq, 'error>)
1616
([<InlineIfLambda>] f: 'okInput -> Result<'okOutput, 'error>)
1717
(xs: 'okInput seq)
18-
=
18+
: Result<'okOutput[], 'error> =
19+
if isNull xs then
20+
nullArg (nameof xs)
21+
1922
match state with
20-
| Error _ -> state
21-
| Ok oks ->
23+
| Error e -> Error e
24+
| Ok initialSuccesses ->
25+
2226
use enumerator = xs.GetEnumerator()
2327

24-
let rec loop oks =
25-
if enumerator.MoveNext() then
26-
match f enumerator.Current with
27-
| Ok ok ->
28-
loop (
29-
seq {
30-
yield ok
31-
yield! oks
32-
}
33-
)
34-
| Error e -> Error e
35-
else
36-
Ok(Seq.rev oks)
28+
let acc = ResizeArray(initialSuccesses)
29+
let mutable err = Unchecked.defaultof<'error>
30+
let mutable ok = true
31+
use e = xs.GetEnumerator()
32+
33+
while ok
34+
&& e.MoveNext() do
35+
match f e.Current with
36+
| Ok r -> acc.Add r
37+
| Error e ->
38+
ok <- false
39+
err <- e
3740

38-
loop oks
41+
if ok then Ok(acc.ToArray()) else Error err
3942

4043
/// <summary>
4144
/// Applies a function to each element of a sequence and returns a single result
4245
/// </summary>
4346
/// <param name="f">The function to apply to each element</param>
4447
/// <param name="xs">The input sequence</param>
45-
/// <returns>A result with the ok elements in a sequence or the first error occurring in the sequence</returns>
46-
/// <remarks>This function is equivalent to <see cref="traverseResultM'"/> but applying and initial state of 'Seq.empty'</remarks>
47-
let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs
48+
/// <returns>A result with the ok elements in an array, or the first error occurring in the sequence</returns>
49+
/// <remarks>This function is equivalent to <see cref="traverseResultM'"/> but applying an initial state of 'Array.empty'</remarks>
50+
let traverseResultM f xs = traverseResultM' (Ok Array.empty) f xs
4851

4952
/// <summary>
5053
/// Converts a sequence of results into a single result
@@ -60,39 +63,49 @@ let sequenceResultM xs = traverseResultM id xs
6063
/// <param name="state">The initial state</param>
6164
/// <param name="f">The function to apply to each element</param>
6265
/// <param name="xs">The input sequence</param>
63-
/// <returns>A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence</returns>
64-
let inline traverseResultA' state ([<InlineIfLambda>] f: 'okInput -> Result<'okOutput, 'error>) xs =
65-
let folder state x =
66-
match state, f x with
67-
| Error errors, Error e ->
68-
seq {
69-
e
70-
yield! Seq.rev errors
71-
}
72-
|> Seq.rev
73-
|> Error
74-
| Ok oks, Ok ok ->
75-
seq {
76-
ok
77-
yield! Seq.rev oks
78-
}
79-
|> Seq.rev
80-
|> Ok
81-
| Ok _, Error e ->
82-
Seq.singleton e
83-
|> Error
84-
| Error _, Ok _ -> state
66+
/// <returns>If no Errors encountered, a result with an array of the ok elements from the 'state' followed by those gathered from the sequence, or an array of all errors from the 'state' and/or those in the sequence</returns>
67+
let inline traverseResultA'
68+
(state: Result<'okOutput seq, 'error seq>)
69+
([<InlineIfLambda>] f: 'okInput -> Result<'okOutput, 'error>)
70+
xs
71+
=
8572

86-
Seq.fold folder state xs
73+
if isNull xs then
74+
nullArg (nameof xs)
75+
76+
match state with
77+
| Error failuresToDate ->
78+
let errs = ResizeArray(failuresToDate)
79+
80+
for x in xs do
81+
match f x with
82+
| Ok _ -> () // as the initial state was failure, oks are irrelevant
83+
| Error e -> errs.Add e
84+
85+
Error(errs.ToArray())
86+
| Ok initialSuccesses ->
87+
88+
let oks = ResizeArray(initialSuccesses)
89+
let errs = ResizeArray()
90+
91+
for x in xs do
92+
match f x with
93+
| Error e -> errs.Add e
94+
| Ok r when errs.Count = 0 -> oks.Add r
95+
| Ok _ -> () // no point saving results we won't use given the end result will be Error
96+
97+
match errs.ToArray() with
98+
| [||] -> Ok(oks.ToArray())
99+
| errs -> Error errs
87100

88101
/// <summary>
89102
/// Applies a function to each element of a sequence and returns a single result
90103
/// </summary>
91104
/// <param name="f">The function to apply to each element</param>
92105
/// <param name="xs">The input sequence</param>
93106
/// <returns>A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence</returns>
94-
/// <remarks>This function is equivalent to <see cref="traverseResultA'"/> but applying and initial state of 'Seq.empty'</remarks>
95-
let traverseResultA f xs = traverseResultA' (Ok Seq.empty) f xs
107+
/// <remarks>This function is equivalent to <see cref="traverseResultA'"/> but applying an initial state of Array.empty'</remarks>
108+
let traverseResultA f xs = traverseResultA' (Ok Array.empty) f xs
96109

97110
/// <summary>
98111
/// Converts a sequence of results into a single result
@@ -146,7 +159,7 @@ let inline traverseAsyncResultM'
146159
/// <param name="f">The function to apply to each element</param>
147160
/// <param name="xs">The input sequence</param>
148161
/// <returns>An async result with the ok elements in a sequence or the first error occurring in the sequence</returns>
149-
/// <remarks>This function is equivalent to <see cref="traverseAsyncResultM'"/> but applying and initial state of 'Seq.empty'</remarks>
162+
/// <remarks>This function is equivalent to <see cref="traverseAsyncResultM'"/> but applying an initial state of 'Seq.empty'</remarks>
150163
let traverseAsyncResultM f xs =
151164
traverseAsyncResultM' (async { return Ok Seq.empty }) f xs
152165

@@ -205,7 +218,7 @@ let inline traverseAsyncResultA'
205218
/// <param name="f">The function to apply to each element</param>
206219
/// <param name="xs">The input sequence</param>
207220
/// <returns>An async result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence</returns>
208-
/// <remarks>This function is equivalent to <see cref="traverseAsyncResultA'"/> but applying and initial state of 'Seq.empty'</remarks>
221+
/// <remarks>This function is equivalent to <see cref="traverseAsyncResultA'"/> but applying an initial state of 'Seq.empty'</remarks>
209222
let traverseAsyncResultA f xs =
210223
traverseAsyncResultA' (async { return Ok Seq.empty }) f xs
211224

@@ -256,7 +269,7 @@ let inline traverseOptionM'
256269
/// <param name="f">The function to apply to each element</param>
257270
/// <param name="xs">The input sequence</param>
258271
/// <returns>An option containing Some sequence of elements or None if any of the function applications return None</returns>
259-
/// <remarks>This function is equivalent to <see cref="traverseOptionM'"/> but applying and initial state of 'Seq.empty'</remarks>
272+
/// <remarks>This function is equivalent to <see cref="traverseOptionM'"/> but applying an initial state of 'Seq.empty'</remarks>
260273
let traverseOptionM f xs = traverseOptionM' (Some Seq.empty) f xs
261274

262275
/// <summary>
@@ -311,7 +324,7 @@ let inline traverseAsyncOptionM'
311324
/// <param name="f">The function to apply to each element</param>
312325
/// <param name="xs">The input sequence</param>
313326
/// <returns>An async option containing Some sequence of elements or None if any of the function applications return None</returns>
314-
/// <remarks>This function is equivalent to <see cref="traverseAsyncOptionM'"/> but applying and initial state of 'Async { return Some Seq.empty }'</remarks>
327+
/// <remarks>This function is equivalent to <see cref="traverseAsyncOptionM'"/> but applying an initial state of 'Async { return Some Seq.empty }'</remarks>
315328
let traverseAsyncOptionM f xs =
316329
traverseAsyncOptionM' (async { return Some Seq.empty }) f xs
317330

@@ -364,7 +377,7 @@ let inline traverseVOptionM'
364377
/// <param name="f">The function to apply to each element</param>
365378
/// <param name="xs">The input sequence</param>
366379
/// <returns>A voption containing Some sequence of elements or None if any of the function applications return None</returns>
367-
/// <remarks>This function is equivalent to <see cref="traverseVOptionM'"/> but applying and initial state of 'ValueSome Seq.empty'</remarks>
380+
/// <remarks>This function is equivalent to <see cref="traverseVOptionM'"/> but applying an initial state of 'ValueSome Seq.empty'</remarks>
368381
let traverseVOptionM f xs =
369382
traverseVOptionM' (ValueSome Seq.empty) f xs
370383

0 commit comments

Comments
 (0)