Skip to content

Commit 4561d77

Browse files
authored
feat(Seq)!: Add sequenceResultA, align sequenceResultM (#255)
* Roll on '24 * refactor(Seq.sequenceResultM)!: Change Ok to Array * docs: sequenceResultM * feat(Seq): sequenceResultA * f sequenceResultM docs * Supress compile error * Fix proposed version
1 parent 08cf82b commit 4561d77

File tree

9 files changed

+275
-58
lines changed

9 files changed

+275
-58
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 5.0.0-alpha.1
2+
- [refactor!: Seq.sequenceResultM returns Array instead of seq](https://github.com/demystifyfp/FsToolkit.ErrorHandling/pull/255) [@bartelink](https://github.com/bartelink)
3+
- [feat(Seq): sequenceResultA](https://github.com/demystifyfp/FsToolkit.ErrorHandling/pull/255) [@bartelink](https://github.com/bartelink)
4+
15
### 4.15.1 - January 15, 2024
26
- [Doc updates](https://github.com/demystifyfp/FsToolkit.ErrorHandling/pull/247) Credits @1eyewonder
37

build/build.fsproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net7.0</TargetFramework>
66
<IsPackable>false</IsPackable>
7+
<!-- <NoWarn>NU1904</NoWarn>-->
8+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
79
</PropertyGroup>
810
<ItemGroup>
911
<Compile Include="DotEnv.fs" />
1012
<Compile Include="build.fs" />
1113
</ItemGroup>
1214
<Import Project="..\.paket\Paket.Restore.targets" />
13-
</Project>
15+
</Project>

gitbook/SUMMARY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
* [sequenceResultM](list/sequenceResultM.md)
2626
* [traverseResultA](list/traverseResultA.md)
2727
* [sequenceResultA](list/sequenceResultA.md)
28+
* Seqs
29+
* [sequenceResultM](seq/sequenceResultM.md)
30+
* [sequenceResultA](seq/sequenceResultA.md)
2831
* Transforms
2932
* [ofChoice](result/ofChoice.md)
3033

gitbook/list/sequenceResultA.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ let checkIfAllPrime (numbers : int list) =
5959
numbers
6060
|> List.map isPrime // Result<bool, string> list
6161
|> List.sequenceResultA // Result<bool list, string list>
62-
|> Result.map (List.forall id) // shortened version of '|> Result.map (fun boolList -> boolList |> List.map (fun x -> x = true))'
62+
|> Result.map (List.forall id) // shortened version of '|> Result.map (fun boolList -> boolList |> List.forall (fun x -> x = true))
6363
6464
let a = [1; 2; 3; 4; 5;] |> checkIfAllPrime
6565
// Error ["1 must be greater than 1"]

gitbook/seq/sequenceResultA.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Seq.sequenceResultA
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
## Function Signature
6+
7+
```fsharp
8+
seq<Result<'a, 'b>> -> Result<'a[], 'b[]>
9+
```
10+
11+
This is applicative, collecting all errors. Compare the example below with [sequenceResultM](sequenceResultM.md).
12+
13+
See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/).
14+
15+
## Examples
16+
17+
### Example 1
18+
19+
```fsharp
20+
// string -> Result<int, string>
21+
let tryParseInt str =
22+
match Int32.TryParse str with
23+
| true, x -> Ok x
24+
| false, _ -> Error $"unable to parse '{str}' to integer"
25+
26+
["1"; "2"; "3"]
27+
|> Seq.map tryParseInt
28+
|> Seq.sequenceResultA
29+
// Ok [| 1; 2; 3 |]
30+
31+
["1"; "foo"; "3"; "bar"]
32+
|> Seq.map tryParseInt
33+
|> Seq.sequenceResultA
34+
// Error [| "unable to parse 'foo' to integer"
35+
// "unable to parse 'bar' to integer" |]
36+
```
37+
38+
### Example 2
39+
40+
```fsharp
41+
// int -> Result<bool, string>
42+
let isPrime (x: int) =
43+
if x < 2 then Error $"{x} must be greater than 1"
44+
elif x = 2 then Ok true
45+
else
46+
let rec isPrime' (x : int) (i : int) =
47+
if i * i > x then Ok true
48+
elif x % i = 0 then Ok false
49+
else isPrime' x (i + 1)
50+
isPrime' x 2
51+
52+
// seq<int> -> Result<bool, string[]>
53+
let checkIfAllPrime (numbers: seq<int>) =
54+
seq { for x in numbers -> isPrime x } // Result<bool, string> seq
55+
|> Seq.sequenceResultA // Result<bool[], string[]>
56+
|> Result.map (Seq.forall id) // shortened version of '|> Result.map (fun results -> results |> Array.forall (fun x -> x = true))'
57+
58+
let a = [| 1; 2; 3; 4; 5 |] |> checkIfAllPrime
59+
// Error [| "1 must be greater than 1" |]
60+
61+
let b = [ 1; 2; 3; 4; 5; 0 ] |> checkIfAllPrime
62+
// Error [| "1 must be greater than 1"; "0 must be greater than 1" |]
63+
64+
let a = seq { 2; 3; 4; 5 } |> checkIfAllPrime
65+
// Ok false
66+
67+
let a = seq { 2; 3; 5 } |> checkIfAllPrime
68+
// Ok true
69+
```

gitbook/seq/sequenceResultM.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Seq.sequenceResultM
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
## Function Signature
6+
7+
```fsharp
8+
seq<Result<'a, 'b>> -> Result<'a[], 'b>
9+
```
10+
11+
This is monadic, stopping on the first error. Compare the example below with [sequenceResultA](sequenceResultA.md).
12+
13+
See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/).
14+
15+
## Examples
16+
17+
### Example 1
18+
19+
```fsharp
20+
// string -> Result<int, string>
21+
let tryParseInt str =
22+
match Int32.TryParse str with
23+
| true, x -> Ok x
24+
| false, _ -> Error $"unable to parse '{str}' to integer"
25+
26+
["1"; "2"; "3"]
27+
|> Seq.map tryParseInt
28+
|> Seq.sequenceResultM
29+
// Ok [| 1; 2; 3 |]
30+
31+
seq { "1"; "foo"; "3"; "bar" }
32+
|> Seq.map tryParseInt
33+
|> Seq.sequenceResultM
34+
// Error "unable to parse 'foo' to integer"
35+
```
36+
37+
### Example 2
38+
39+
```fsharp
40+
// int -> Result<bool, string>
41+
let isPrime (x: int) =
42+
if x < 2 then Error $"{x} must be greater than 1"
43+
elif x = 2 then Ok true
44+
else
45+
let rec isPrime' (x : int) (i : int) =
46+
if i * i > x then Ok true
47+
elif x % i = 0 then Ok false
48+
else isPrime' x (i + 1)
49+
isPrime' x 2
50+
51+
// int seq -> Result<bool, string[]>
52+
let checkIfAllPrime (numbers: seq<int>) =
53+
numbers
54+
|> Seq.map isPrime // seq<Result<bool, string>>
55+
|> Seq.sequenceResultM // Result<bool[], string>
56+
|> Result.map (Array.forall id) // shortened version of '|> Result.map (fun bools -> bools |> Array.forall (fun x -> x = true))'
57+
58+
let a = [ 1; 2; 3; 4; 5 ] |> checkIfAllPrime
59+
// Error [| "1 must be greater than 1" |]
60+
61+
let b = [| 1; 2; 3; 4; 5; 0 |] |> checkIfAllPrime
62+
// Error [| "1 must be greater than 1" |]
63+
64+
let a = seq { 2; 3; 4; 5 } |> checkIfAllPrime
65+
// Ok false
66+
67+
let a = [2; 3; 5;] |> checkIfAllPrime
68+
// Ok true
69+
```

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<PropertyGroup>
55
<Description>FsToolkit.ErrorHandling is an extensive utility library based around the F# Result type, enabling consistent and powerful error handling.</Description>
66
<Authors>demystifyfp, TheAngryByrd</Authors>
7-
<Copyright>Copyright © 2018-23</Copyright>
7+
<Copyright>Copyright © 2018-24</Copyright>
88
<PackageProjectUrl>https://demystifyfp.gitbook.io/fstoolkit-errorhandling</PackageProjectUrl>
99
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1010
<PackageReadmeFile>README.md</PackageReadmeFile> <!--https://docs.microsoft.com/en-gb/nuget/reference/msbuild-targets#packagereadmefile -->

src/FsToolkit.ErrorHandling/Seq.fs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
1-
namespace FsToolkit.ErrorHandling
2-
31
[<RequireQualifiedAccess>]
4-
module Seq =
5-
6-
let sequenceResultM (xs: seq<Result<'t, 'e>>) : Result<'t seq, 'e> =
7-
let rec loop xs ts =
8-
match Seq.tryHead xs with
9-
| Some x ->
10-
x
11-
|> Result.bind (fun t -> loop (Seq.tail xs) (t :: ts))
12-
| None ->
13-
Ok(
14-
List.rev ts
15-
|> List.toSeq
16-
)
17-
18-
// Seq.cache prevents double evaluation in Seq.tail
19-
loop (Seq.cache xs) []
2+
module FsToolkit.ErrorHandling.Seq
3+
4+
let sequenceResultM (xs: seq<Result<'t, 'e>>) : Result<'t[], 'e> =
5+
if isNull xs then
6+
nullArg (nameof xs)
7+
8+
let acc = ResizeArray()
9+
let mutable err = Unchecked.defaultof<_>
10+
let mutable ok = true
11+
use e = xs.GetEnumerator()
12+
13+
while ok
14+
&& e.MoveNext() do
15+
match e.Current with
16+
| Ok r -> acc.Add r
17+
| Error e ->
18+
ok <- false
19+
err <- e
20+
21+
if ok then Ok(acc.ToArray()) else Error err
22+
23+
let sequenceResultA (xs: seq<Result<'t, 'e>>) : Result<'t[], 'e[]> =
24+
if isNull xs then
25+
nullArg (nameof xs)
26+
27+
let oks = ResizeArray()
28+
let errs = ResizeArray()
29+
30+
for x in xs do
31+
match x with
32+
| Ok r -> oks.Add r
33+
| Error e -> errs.Add e
34+
35+
match errs.ToArray() with
36+
| [||] -> Ok(oks.ToArray())
37+
| errs -> Error errs

0 commit comments

Comments
 (0)