Skip to content

Commit 1cdaee5

Browse files
njlrTheAngryByrd
authored andcommitted
* Adds Seq module for lazy result traversal
1 parent 399f5e1 commit 1cdaee5

File tree

5 files changed

+75
-3
lines changed

5 files changed

+75
-3
lines changed

src/FsToolkit.ErrorHandling/FsToolkit.ErrorHandling.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<Compile Include="AsyncOptionCE.fs" />
3232
<Compile Include="AsyncOptionOp.fs" />
3333
<Compile Include="List.fs" />
34+
<Compile Include="Seq.fs" />
3435
<None Include="Script.fsx" />
3536
</ItemGroup>
3637

@@ -39,6 +40,6 @@
3940
<Content Include="*.fsproj; **\*.fs" PackagePath="fable\" />
4041
</ItemGroup>
4142

42-
43+
4344
<Import Project="..\..\.paket\Paket.Restore.targets" />
4445
</Project>

src/FsToolkit.ErrorHandling/Seq.fs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace FsToolkit.ErrorHandling
2+
3+
[<RequireQualifiedAccess>]
4+
module Seq =
5+
6+
let sequenceResultM (xs : seq<Result<'t, 'e>>) : Result<'t list, 'e> =
7+
let rec loop xs ts =
8+
match Seq.tryHead xs with
9+
| Some x ->
10+
x
11+
|> Result.bind (fun t ->
12+
loop (Seq.tail xs) (t :: ts))
13+
| None ->
14+
Ok (List.rev ts)
15+
16+
// Seq.cache prevents double evaluation in Seq.tail
17+
loop (Seq.cache xs) []

tests/FsToolkit.ErrorHandling.Tests/FsToolkit.ErrorHandling.Tests.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323
<Compile Include="AsyncOption.fs" />
2424
<Compile Include="AsyncOptionCE.fs" />
2525
<Compile Include="List.fs" />
26+
<Compile Include="Seq.fs" />
2627
<Compile Include="AsyncResult.fs" />
2728
<Compile Include="AsyncResultCE.fs" />
2829
<Compile Include="AsyncResultOption.fs" />
2930
<Compile Include="Validation.fs" />
3031
<Compile Include="ValidationCE.fs" />
3132
<Compile Include="Main.fs" />
3233
</ItemGroup>
33-
34+
3435
<Import Project="..\..\.paket\Paket.Restore.targets" />
3536
</Project>

tests/FsToolkit.ErrorHandling.Tests/Main.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let allTests = testList "All Tests" [
1515
AsyncOptionTests.allTests
1616
AsyncOptionCETests.allTests
1717
ListTests.allTests
18+
SeqTests.allTests
1819
AsyncResultTests.allTests
1920
AsyncResultCETests.allTests
2021
AsyncResultOptionTests.allTests
@@ -27,5 +28,5 @@ let main argv =
2728
#if FABLE_COMPILER
2829
Mocha.runTests allTests
2930
#else
30-
Tests.runTestsWithArgs defaultConfig argv allTests
31+
Tests.runTestsWithArgs defaultConfig argv allTests
3132
#endif
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module SeqTests
2+
3+
4+
#if FABLE_COMPILER
5+
open Fable.Mocha
6+
#else
7+
open Expecto
8+
#endif
9+
open SampleDomain
10+
open TestData
11+
open TestHelpers
12+
open System
13+
open FsToolkit.ErrorHandling
14+
15+
16+
17+
let sequenceResultMTests =
18+
testList "Seq.sequenceResultM Tests" [
19+
testCase "traverseResult with an empty sequence" <| fun _ ->
20+
let tweets = []
21+
let expected = Ok []
22+
let actual = Seq.sequenceResultM (Seq.map Tweet.TryCreate tweets)
23+
Expect.equal actual expected "Should have an empty list of valid tweets"
24+
25+
testCase "traverseResult with a sequence of valid data" <| fun _ ->
26+
let tweets = ["Hi"; "Hello"; "Hola"]
27+
let expected = List.map tweet tweets |> Ok
28+
let actual = Seq.sequenceResultM (Seq.map Tweet.TryCreate tweets)
29+
Expect.equal actual expected "Should have a list of valid tweets"
30+
31+
testCase "sequenceResultM with few invalid data" <| fun _ ->
32+
let tweets = [""; "Hello"; aLongerInvalidTweet] :> seq<_>
33+
let actual = Seq.sequenceResultM (Seq.map Tweet.TryCreate tweets)
34+
Expect.equal actual (Error emptyTweetErrMsg) "traverse the sequence and return the first error"
35+
36+
testCase "sequenceResultM stops after first invalid data" <| fun _ ->
37+
let mutable counter = 0
38+
let tweets = seq {
39+
"Hi"
40+
"Hello"
41+
"Hola"
42+
aLongerInvalidTweet
43+
counter <- counter + 1
44+
}
45+
let actual = Seq.sequenceResultM (Seq.map Tweet.TryCreate tweets)
46+
Expect.equal actual (Error longerTweetErrMsg) "traverse the sequence and return the first error"
47+
Expect.equal counter 0 "evaluation of the sequence stops at the first error"
48+
]
49+
50+
let allTests = testList "Seq Tests" [
51+
sequenceResultMTests
52+
]

0 commit comments

Comments
 (0)