Skip to content

Commit 5ab3c09

Browse files
committed
Implement sequence/traverse operations for Task and TaskResult in the Option module
1 parent b85be8b commit 5ab3c09

File tree

7 files changed

+393
-0
lines changed

7 files changed

+393
-0
lines changed

gitbook/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@
4444
* [map3](option/map3.md)
4545
* [sequenceAsync](option/sequenceAsync.md)
4646
* [sequenceResult](option/sequenceResult.md)
47+
* [sequenceTask](option/sequenceTask.md)
48+
* [sequenceTaskResult](option/sequenceTaskResult.md)
4749
* [tee Functions](option/teeFunctions.md)
4850
* [traverseAsync](option/traverseAsync.md)
4951
* [traverseResult](option/traverseResult.md)
52+
* [traverseTask](option/traverseTask.md)
53+
* [traverseTaskResult](option/traverseTaskResult.md)
5054
* [zip](option/zip.md)
5155
* Lists
5256
* [traverseOptionM](option/traverseOptionM.md)

gitbook/option/sequenceTask.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Option.sequenceTask
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
Function Signature:
6+
7+
```fsharp
8+
Task<'a> option -> Task<'a option>
9+
```
10+
11+
Note that `sequence` is the same as `traverse id`. See also [Option.traverseTask](traverseTask.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+
let a1 : Task<int option> =
21+
Option.sequenceTask (Some (Task.singleton 42))
22+
// async { return Some 42 }
23+
24+
let a2 : Task<int option> =
25+
Option.sequenceTask None
26+
// async { return None }
27+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## Option.sequenceTaskResult
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
Function Signature:
6+
7+
```fsharp
8+
Task<Result<'a, 'e>> option -> Task<Result<'a option>, 'e>
9+
```
10+
11+
Note that `sequence` is the same as `traverse id`. See also [Option.traverseTaskResult](traverseTaskResult.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+
let r1 : Task<Result<int option, string>> =
21+
Some (task { return Ok 42 }) |> Option.sequenceTaskResult
22+
// task { return Ok (Some 42) }
23+
24+
let r2 : Task<Result<int option, string>> =
25+
Some (task { return Error "something went wrong" }) |> Option.sequenceTaskResult
26+
// task { return Error "something went wrong" }
27+
28+
let r3 : Task<Result<int option, string>> =
29+
None |> Option.sequenceTaskResult
30+
// task { return Ok None }
31+
```

gitbook/option/traverseTask.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Option.traverseTask
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
Function Signature:
6+
7+
```fsharp
8+
('a -> Task<'b>) -> 'a option -> Task<'b option>
9+
```
10+
11+
Note that `traverse` is the same as `map >> sequence`. See also [Option.sequenceTask](sequenceTask.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+
Let's assume we have a type `Customer`:
20+
21+
```fsharp
22+
type Customer = {
23+
Id : int
24+
Email : string
25+
}
26+
```
27+
28+
And we have a function called `getCustomerByEmail` that retrieves a `Customer` by email address asynchronously from some external source -- a database, a web service, etc:
29+
30+
```fsharp
31+
// string -> Task<Customer>
32+
let getCustomerByEmail email : Task<Customer> = task {
33+
return { Id = 1; Email = "[email protected]" } // return a constant for simplicity
34+
}
35+
```
36+
37+
If we have a value of type `string option` and want to call the `getCustomerByEmail` function, we can achieve it using the `traverseTask` function as below:
38+
39+
```fsharp
40+
Some "[email protected]" |> Option.traverseTask getCustomerByEmail
41+
// task { return Some { Id = 1; Email = "[email protected]" } }
42+
43+
None |> Option.traverseTask getCustomerByEmail
44+
// task { return None }
45+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
## Option.traverseTaskResult
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
Function Signature:
6+
7+
```fsharp
8+
('a -> Task<Result<'b,'c>>) -> 'a option -> Task<Result<'b option, 'c>>
9+
```
10+
11+
Note that `traverse` is the same as `map >> sequence`. See also [Option.sequenceTaskResult](sequenceTaskResult.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+
Say we have a function to get a number from a database (asynchronously), and multiply our input by that number if it's found:
20+
21+
```fsharp
22+
let tryMultiplyWithDatabaseValue: float -> Task<Result<float, string>> = // ...
23+
```
24+
25+
If we start with an optional value, then we could map this function using `Option.traverseTaskResult` as follows:
26+
27+
```fsharp
28+
let input = Some 1.234
29+
30+
input // float option
31+
|> Option.traverseTaskResult tryMultiplyWithDatabaseValue // Task<Result<float option, string>>
32+
```
33+
34+
If we combine this with the [TaskResult computation expression](../taskResult/ce.md), we could directly `let!` the output:
35+
36+
```fsharp
37+
taskResult {
38+
let input = Some 1.234
39+
40+
let! output = // float option
41+
input // float option
42+
|> Option.traverseTaskResult tryMultiplyWithDatabaseValue // Task<Result<float option, string>>
43+
}
44+
```

src/FsToolkit.ErrorHandling/Option.fs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,71 @@ module Option =
321321

322322
opt
323323

324+
#if !FABLE_COMPILER
325+
open System.Threading.Tasks
326+
327+
/// <summary>
328+
/// Converts an <c>Option&lt;Task&lt;_&gt;&gt;</c> to an <c>Task&lt;Option&lt;_&gt;&gt;</c><br />
329+
///
330+
/// Documentation is found here: <see href="https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/sequencetask" />
331+
/// </summary>
332+
let inline sequenceTask (optTask: Option<Task<'T>>) : Task<Option<'T>> =
333+
task {
334+
match optTask with
335+
| Some tsk ->
336+
let! x = tsk
337+
return Some x
338+
| None -> return None
339+
}
340+
341+
/// <summary>
342+
/// Maps a <c>Task</c> function over an <c>option</c>, returning a <c>Task&lt;'T option&gt;</c><br/>
343+
///
344+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/traversetask</href>
345+
/// </summary>
346+
/// <param name="f">The function to map over the <c>option</c>.</param>
347+
/// <param name="opt">The <c>option</c> to map over.</param>
348+
/// <returns>A <c>Task&lt;'T option&gt;</c> with the mapped value.</returns>
349+
let inline traverseTask
350+
([<InlineIfLambda>] f: 'T -> Task<'T>)
351+
(opt: Option<'T>)
352+
: Task<Option<'T>> =
353+
sequenceTask ((map f) opt)
354+
355+
/// <summary>
356+
/// Converts an <c>Async&lt;Result&lt;'ok,'error&gt;&gt; option</c> to an <c>Async&lt;Result&lt;'ok option,'error&gt;&gt;</c><br />
357+
///
358+
/// Documentation is found here: <see href="https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/sequencetaskresult" />
359+
/// </summary>
360+
let inline sequenceTaskResult
361+
(optTaskResult: Task<Result<'T, 'E>> option)
362+
: Task<Result<'T option, 'E>> =
363+
task {
364+
match optTaskResult with
365+
| Some taskRes ->
366+
let! xRes = taskRes
367+
368+
return
369+
xRes
370+
|> Result.map Some
371+
| None -> return Ok None
372+
}
373+
374+
/// <summary>
375+
/// Maps a <c>TaskResult</c> function over an <c>option</c>, returning a <c>Task&lt;Result&lt;'U option, 'E&gt;&gt;</c>.<br />
376+
///
377+
/// Documentation is found here: <see href="https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/traversetaskresult" />
378+
/// </summary>
379+
/// <param name="f">The function to map over the <c>option</c>.</param>
380+
/// <param name="opt">The <c>option</c> to map over.</param>
381+
/// <returns>A <c>Task&lt;Result&lt;'U option, 'E&gt;&gt;</c> with the mapped value.</returns>
382+
let inline traverseTaskResult
383+
([<InlineIfLambda>] f: 'T -> Task<Result<'U, 'E>>)
384+
(opt: 'T option)
385+
: Task<Result<'U option, 'E>> =
386+
sequenceTaskResult ((map f) opt)
387+
#endif
388+
324389
/// <summary>
325390
/// Converts a Option<Async<_>> to an Async<Option<_>>
326391
///

0 commit comments

Comments
 (0)