Skip to content

Commit fa8009e

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

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,40 +10,43 @@ 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>
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 'Seq.empty'</remarks>
4750
let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs
4851

4952
/// <summary>
@@ -60,45 +63,55 @@ 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, an Ok result bearing an array of the ok elements from the 'state' followed by those gathered from the sequence, or an Error bearing 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>
93-
/// <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>
106+
/// <returns>A result with the ok elements in an array or an array of all errors from across the 'state' and the sequence</returns>
107+
/// <remarks>This function is equivalent to <see cref="traverseResultA'"/> but applying an initial state of Seq.empty'</remarks>
95108
let traverseResultA f xs = traverseResultA' (Ok Seq.empty) f xs
96109

97110
/// <summary>
98111
/// Converts a sequence of results into a single result
99112
/// </summary>
100113
/// <param name="xs">The input sequence</param>
101-
/// <returns>A result with the ok elements in a sequence or a sequence of all errors occuring in the original sequence</returns>
114+
/// <returns>A result with the ok elements in an array or an array of all errors from across the 'state' and the sequence</returns>
102115
/// <remarks>This function is equivalent to <see cref="traverseResultA"/> but auto-applying the 'id' function</remarks>
103116
let sequenceResultA xs = traverseResultA id xs
104117

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