Skip to content

Commit 2b557b5

Browse files
authored
Merge pull request #11 from insurello/early-return-on-error
Optimize traverse
2 parents 82ae7d7 + 1115022 commit 2b557b5

File tree

3 files changed

+26
-6
lines changed

3 files changed

+26
-6
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ Solves the same problem as [`bind2`](#bind2) but for five arguments.
299299
sequence : List<AsyncResult<'a, 'error>> -> AsyncResult<'a list, 'error>
300300
```
301301

302-
From time to time you will find yourself having a list of `AsyncResult` (`List<AsyncResult<'a, 'err>`) but you would rather have an `AsyncResult` with a list of values (`AsyncResult<'a list, 'error>`). In those cases `sequence` can help you.
302+
From time to time you will find yourself having a list of `AsyncResult` (`List<AsyncResult<'a, 'err>`) but you would rather have an `AsyncResult` with a list of values (`AsyncResult<'a list, 'error>`). In those cases `sequence` can help you. `sequence` will make an early return if it reaches an `Error`.
303303

304304
```fsharp
305305
fetchUser : int -> AsyncResult<User, string>
@@ -315,7 +315,7 @@ userIds // [1; 2; 3]
315315
traverse : ('a -> 'b) -> List<AsyncResult<'a, 'err>> -> AsyncResult<'b list, 'err>
316316
```
317317

318-
Similar to [`sequence`](#sequence), `traverse` will also change the type from a list of `AsyncResult` to an `AsyncResult` with a list. The difference is that `traverse` allows you to use a transformation function to transform each value in the list of `AsyncResult`.
318+
Similar to [`sequence`](#sequence), `traverse` will also change the type from a list of `AsyncResult` to an `AsyncResult` with a list. The difference is that `traverse` allows you to use a transformation function to transform each value in the list of `AsyncResult`. `traverse` will make an early return if it reaches an `Error`.
319319

320320
By sending in `id` as the transform function you have implemented `sequence`. Let's have a look how we can solve the example in the `sequence` description using `traverse` instead.
321321

src/Insurello.AsyncExtra.Tests/AsyncExtraTests.fs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ let traverseTests =
9494
let! actual = AsyncResult.traverse transformer input
9595
Expect.equal actual expected "should equal"
9696
}
97+
testAsync "should make an early return if there is an Error" {
98+
let mutable counter = 0
99+
100+
let transformer x =
101+
counter <- counter + x
102+
x
103+
104+
let expected = 1
105+
106+
let input =
107+
[ (AsyncResult.singleton 1)
108+
(AsyncResult.fromResult (Error "Skip next"))
109+
(AsyncResult.singleton 3) ]
110+
111+
let! _ = AsyncResult.traverse transformer input
112+
113+
Expect.equal counter expected "should equal"
114+
}
97115
testAsync "should execute async task in sequence" {
98116
let mutable orderRun = []
99117

src/Insurello.AsyncExtra/AsyncExtra.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@ module AsyncResult =
7777

7878
let traverse : ('a -> 'b) -> List<AsyncResult<'a, 'err>> -> AsyncResult<'b list, 'err> =
7979
fun transformer list ->
80-
let cons head tail = head :: tail
80+
let cons tail head = head :: tail
8181

82-
let mapConcat headR tailR =
83-
apply (map (transformer >> cons) headR) tailR
82+
let rec fold remaining acc =
83+
match remaining with
84+
| [] -> acc |> List.rev |> singleton
85+
| xA :: xAs -> xA |> bind (transformer >> cons acc >> fold xAs)
8486

85-
List.foldBack mapConcat list (singleton [])
87+
fold list []
8688

8789
let sequence : List<AsyncResult<'a, 'error>> -> AsyncResult<'a list, 'error> = fun list -> traverse id list
8890

0 commit comments

Comments
 (0)