diff --git a/src/FsToolkit.ErrorHandling/List.fs b/src/FsToolkit.ErrorHandling/List.fs
index c6e69181..974cefe0 100644
--- a/src/FsToolkit.ErrorHandling/List.fs
+++ b/src/FsToolkit.ErrorHandling/List.fs
@@ -52,7 +52,6 @@ module List =
let sequenceAsyncResultM xs = traverseAsyncResultM id xs
-
let rec private traverseResultA' state f xs =
match xs with
| [] ->
@@ -115,8 +114,52 @@ module List =
let sequenceValidationA xs = traverseValidationA id xs
-
let traverseAsyncResultA f xs =
traverseAsyncResultA' (AsyncResult.retn []) f xs
let sequenceAsyncResultA xs = traverseAsyncResultA id xs
+
+ let rec private traverseOptionM' (state: Option<_>) (f: _ -> Option<_>) xs =
+ match xs with
+ | [] ->
+ state
+ |> Option.map List.rev
+ | x :: xs ->
+ let r =
+ option {
+ let! y = f x
+ let! ys = state
+ return y :: ys
+ }
+
+ match r with
+ | Some _ -> traverseOptionM' r f xs
+ | None -> r
+
+ let rec private traverseAsyncOptionM' (state: Async>) (f: _ -> Async >) xs =
+ match xs with
+ | [] ->
+ state
+ |> AsyncOption.map List.rev
+ | x :: xs ->
+ async {
+ let! o =
+ asyncOption {
+ let! y = f x
+ let! ys = state
+ return y :: ys
+ }
+
+ match o with
+ | Some _ -> return! traverseAsyncOptionM' (Async.singleton o) f xs
+ | None -> return o
+ }
+
+ let traverseOptionM f xs = traverseOptionM' (Some []) f xs
+
+ let sequenceOptionM xs = traverseOptionM id xs
+
+ let traverseAsyncOptionM f xs =
+ traverseAsyncOptionM' (AsyncOption.retn []) f xs
+
+ let sequenceAsyncOptionM xs = traverseAsyncOptionM id xs
diff --git a/tests/FsToolkit.ErrorHandling.Tests/List.fs b/tests/FsToolkit.ErrorHandling.Tests/List.fs
index 8706e41d..1ecfbc59 100644
--- a/tests/FsToolkit.ErrorHandling.Tests/List.fs
+++ b/tests/FsToolkit.ErrorHandling.Tests/List.fs
@@ -47,6 +47,39 @@ let traverseResultMTests =
"traverse the list and return the first error"
]
+let traverseOptionMTests =
+ testList "List.traverseOptionM Tests" [
+ let tryTweetOption x =
+ match x with
+ | x when String.IsNullOrEmpty x -> None
+ | _ -> Some x
+
+ testCase "traverseOption with a list of valid data"
+ <| fun _ ->
+ let tweets = [
+ "Hi"
+ "Hello"
+ "Hola"
+ ]
+
+ let expected = Some tweets
+ let actual = List.traverseOptionM tryTweetOption tweets
+
+ Expect.equal actual expected "Should have a list of valid tweets"
+
+ testCase "traverseOption with few invalid data"
+ <| fun _ ->
+ let tweets = [
+ "Hi"
+ "Hello"
+ String.Empty
+ ]
+
+ let expected = None
+ let actual = List.traverseOptionM tryTweetOption tweets
+
+ Expect.equal actual expected "traverse the list and return none"
+ ]
let sequenceResultMTests =
testList "List.sequenceResultM Tests" [
@@ -82,6 +115,38 @@ let sequenceResultMTests =
"traverse the list and return the first error"
]
+let sequenceOptionMTests =
+ testList "List.sequenceOptionM Tests" [
+ let tryTweetOption x =
+ match x with
+ | x when String.IsNullOrEmpty x -> None
+ | _ -> Some x
+
+ testCase "traverseOption with a list of valid data"
+ <| fun _ ->
+ let tweets = [
+ "Hi"
+ "Hello"
+ "Hola"
+ ]
+
+ let expected = Some tweets
+ let actual = List.sequenceOptionM (List.map tryTweetOption tweets)
+
+ Expect.equal actual expected "Should have a list of valid tweets"
+
+ testCase "sequenceOptionM with few invalid data"
+ <| fun _ ->
+ let tweets = [
+ String.Empty
+ "Hello"
+ String.Empty
+ ]
+
+ let actual = List.sequenceOptionM (List.map tryTweetOption tweets)
+
+ Expect.equal actual None "traverse the list and return none"
+ ]
let traverseResultATests =
testList "List.traverseResultA Tests" [
@@ -285,6 +350,38 @@ let traverseAsyncResultMTests =
}
]
+let traverseAsyncOptionMTests =
+
+ let userIds = [
+ userId1
+ userId2
+ userId3
+ ]
+
+ testList "List.traverseAsyncOptionM Tests" [
+ testCaseAsync "traverseAsyncOptionM with a list of valid data"
+ <| async {
+ let expected = Some userIds
+ let f x = async { return Some x }
+ let actual = List.traverseAsyncOptionM f userIds
+
+ match expected with
+ | Some e -> do! Expect.hasAsyncSomeValue e actual
+ | None -> failwith "Error in the test case code"
+ }
+
+ testCaseAsync "traverseOptionA with few invalid data"
+ <| async {
+ let expected = None
+ let f _ = async { return None }
+ let actual = List.traverseAsyncOptionM f userIds
+
+ match expected with
+ | Some _ -> failwith "Error in the test case code"
+ | None -> do! Expect.hasAsyncNoneValue actual
+ }
+ ]
+
let notifyFailure (PostId _) (UserId uId) =
async {
if
@@ -370,6 +467,43 @@ let sequenceAsyncResultMTests =
}
]
+let sequenceAsyncOptionMTests =
+
+ let userIds = [
+ userId1
+ userId2
+ userId3
+ ]
+
+ testList "List.sequenceAsyncOptionM Tests" [
+ testCaseAsync "sequenceAsyncOptionM with a list of valid data"
+ <| async {
+ let expected = Some userIds
+ let f x = async { return Some x }
+
+ let actual =
+ List.map f userIds
+ |> List.sequenceAsyncOptionM
+
+ match expected with
+ | Some e -> do! Expect.hasAsyncSomeValue e actual
+ | None -> failwith "Error in the test case code"
+ }
+
+ testCaseAsync "sequenceOptionA with few invalid data"
+ <| async {
+ let expected = None
+ let f _ = async { return None }
+
+ let actual =
+ List.map f userIds
+ |> List.sequenceAsyncOptionM
+
+ match expected with
+ | Some _ -> failwith "Error in the test case code"
+ | None -> do! Expect.hasAsyncNoneValue actual
+ }
+ ]
let sequenceAsyncResultATests =
let userIds =
@@ -412,13 +546,17 @@ let sequenceAsyncResultATests =
let allTests =
testList "List Tests" [
traverseResultMTests
+ traverseOptionMTests
sequenceResultMTests
+ sequenceOptionMTests
traverseResultATests
sequenceResultATests
traverseValidationATests
sequenceValidationATests
traverseAsyncResultMTests
+ traverseAsyncOptionMTests
traverseAsyncResultATests
sequenceAsyncResultMTests
+ sequenceAsyncOptionMTests
sequenceAsyncResultATests
]