Skip to content

Commit 81b9eff

Browse files
1eyewonderTheAngryByrd
authored andcommitted
Added traverse and sequence functions for Option
1 parent 2807702 commit 81b9eff

File tree

2 files changed

+187
-2
lines changed
  • src/FsToolkit.ErrorHandling
  • tests/FsToolkit.ErrorHandling.Tests

2 files changed

+187
-2
lines changed

src/FsToolkit.ErrorHandling/List.fs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ module List =
5252

5353
let sequenceAsyncResultM xs = traverseAsyncResultM id xs
5454

55-
5655
let rec private traverseResultA' state f xs =
5756
match xs with
5857
| [] ->
@@ -115,8 +114,51 @@ module List =
115114

116115
let sequenceValidationA xs = traverseValidationA id xs
117116

118-
119117
let traverseAsyncResultA f xs =
120118
traverseAsyncResultA' (AsyncResult.retn []) f xs
121119

122120
let sequenceAsyncResultA xs = traverseAsyncResultA id xs
121+
122+
let rec private traverseOptionM' (state: Option<_>) (f: _ -> Option<_>) xs =
123+
match xs with
124+
| [] ->
125+
state
126+
|> Option.map List.rev
127+
| x :: xs ->
128+
let r =
129+
option {
130+
let! y = f x
131+
let! ys = state
132+
return y :: ys
133+
}
134+
135+
match r with
136+
| Some _ -> traverseOptionM' r f xs
137+
| None -> r
138+
139+
let rec private traverseAsyncOptionM' (state: Async<Option<_>>) (f: _ -> Async<Option<_>>) xs =
140+
match xs with
141+
| [] ->
142+
state
143+
|> AsyncOption.map List.rev
144+
| x :: xs ->
145+
async {
146+
let! o =
147+
asyncOption {
148+
let! y = f x
149+
let! ys = state
150+
return y :: ys
151+
}
152+
153+
match o with
154+
| Some _ -> return! traverseAsyncOptionM' (Async.singleton o) f xs
155+
| None -> return o
156+
}
157+
158+
let traverseOptionM f xs = traverseOptionM' (Some []) f xs
159+
160+
let sequenceOptionM xs = traverseOptionM id xs
161+
162+
let traverseAsyncOptionM f xs = traverseAsyncOptionM' (AsyncOption.retn []) f xs
163+
164+
let sequenceAsyncOptionM xs = traverseAsyncOptionM id xs

tests/FsToolkit.ErrorHandling.Tests/List.fs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,45 @@ let traverseResultMTests =
4747
"traverse the list and return the first error"
4848
]
4949

50+
let traverseOptionMTests =
51+
testList "List.traverseOptionM Tests" [
52+
let tryTweetOption x =
53+
match x with
54+
| x when String.IsNullOrEmpty x -> None
55+
| _ -> Some x
56+
57+
testCase "traverseOption with a list of valid data"
58+
<| fun _ ->
59+
let tweets = [
60+
"Hi"
61+
"Hello"
62+
"Hola"
63+
]
64+
65+
let expected = Some tweets
66+
let actual = List.traverseOptionM tryTweetOption tweets
67+
68+
Expect.equal
69+
actual
70+
expected
71+
"Should have a list of valid tweets"
72+
73+
testCase "traverseOption with few invalid data"
74+
<| fun _ ->
75+
let tweets = [
76+
"Hi"
77+
"Hello"
78+
String.Empty
79+
]
80+
81+
let expected = None
82+
let actual = List.traverseOptionM tryTweetOption tweets
83+
84+
Expect.equal
85+
actual
86+
expected
87+
"traverse the list and return none"
88+
]
5089

5190
let sequenceResultMTests =
5291
testList "List.sequenceResultM Tests" [
@@ -82,6 +121,41 @@ let sequenceResultMTests =
82121
"traverse the list and return the first error"
83122
]
84123

124+
let sequenceOptionMTests =
125+
testList "List.sequenceOptionM Tests" [
126+
let tryTweetOption x =
127+
match x with
128+
| x when String.IsNullOrEmpty x -> None
129+
| _ -> Some x
130+
131+
testCase "traverseOption with a list of valid data"
132+
<| fun _ ->
133+
let tweets = [
134+
"Hi"
135+
"Hello"
136+
"Hola"
137+
]
138+
139+
let expected = Some tweets
140+
let actual = List.sequenceOptionM (List.map tryTweetOption tweets)
141+
142+
Expect.equal actual expected "Should have a list of valid tweets"
143+
144+
testCase "sequenceOptionM with few invalid data"
145+
<| fun _ ->
146+
let tweets = [
147+
String.Empty
148+
"Hello"
149+
String.Empty
150+
]
151+
152+
let actual = List.sequenceOptionM (List.map tryTweetOption tweets)
153+
154+
Expect.equal
155+
actual
156+
None
157+
"traverse the list and return none"
158+
]
85159

86160
let traverseResultATests =
87161
testList "List.traverseResultA Tests" [
@@ -285,6 +359,39 @@ let traverseAsyncResultMTests =
285359
}
286360
]
287361

362+
let traverseAsyncOptionMTests =
363+
364+
let userIds =
365+
[
366+
userId1
367+
userId2
368+
userId3
369+
]
370+
371+
testList "List.traverseAsyncOptionM Tests" [
372+
testCaseAsync "traverseAsyncOptionM with a list of valid data"
373+
<| async {
374+
let expected = Some userIds
375+
let f x = async { return Some x }
376+
let actual = List.traverseAsyncOptionM f userIds
377+
378+
match expected with
379+
| Some e -> do! Expect.hasAsyncSomeValue e actual
380+
| None -> failwith "Error in the test case code"
381+
}
382+
383+
testCaseAsync "traverseOptionA with few invalid data"
384+
<| async {
385+
let expected = None
386+
let f _ = async { return None }
387+
let actual = List.traverseAsyncOptionM f userIds
388+
389+
match expected with
390+
| Some _ -> failwith "Error in the test case code"
391+
| None -> do! Expect.hasAsyncNoneValue actual
392+
}
393+
]
394+
288395
let notifyFailure (PostId _) (UserId uId) =
289396
async {
290397
if
@@ -370,6 +477,38 @@ let sequenceAsyncResultMTests =
370477
}
371478
]
372479

480+
let sequenceAsyncOptionMTests =
481+
482+
let userIds =
483+
[
484+
userId1
485+
userId2
486+
userId3
487+
]
488+
489+
testList "List.sequenceAsyncOptionM Tests" [
490+
testCaseAsync "sequenceAsyncOptionM with a list of valid data"
491+
<| async {
492+
let expected = Some userIds
493+
let f x = async { return Some x }
494+
let actual = List.map f userIds |> List.sequenceAsyncOptionM
495+
496+
match expected with
497+
| Some e -> do! Expect.hasAsyncSomeValue e actual
498+
| None -> failwith "Error in the test case code"
499+
}
500+
501+
testCaseAsync "sequenceOptionA with few invalid data"
502+
<| async {
503+
let expected = None
504+
let f _ = async { return None }
505+
let actual = List.map f userIds |> List.sequenceAsyncOptionM
506+
507+
match expected with
508+
| Some _ -> failwith "Error in the test case code"
509+
| None -> do! Expect.hasAsyncNoneValue actual
510+
}
511+
]
373512

374513
let sequenceAsyncResultATests =
375514
let userIds =
@@ -412,13 +551,17 @@ let sequenceAsyncResultATests =
412551
let allTests =
413552
testList "List Tests" [
414553
traverseResultMTests
554+
traverseOptionMTests
415555
sequenceResultMTests
556+
sequenceOptionMTests
416557
traverseResultATests
417558
sequenceResultATests
418559
traverseValidationATests
419560
sequenceValidationATests
420561
traverseAsyncResultMTests
562+
traverseAsyncOptionMTests
421563
traverseAsyncResultATests
422564
sequenceAsyncResultMTests
565+
sequenceAsyncOptionMTests
423566
sequenceAsyncResultATests
424567
]

0 commit comments

Comments
 (0)