diff --git a/gitbook/option/sequenceAsync.md b/gitbook/option/sequenceAsync.md new file mode 100644 index 00000000..7b54395a --- /dev/null +++ b/gitbook/option/sequenceAsync.md @@ -0,0 +1,27 @@ +## Option.sequenceAsync + +Namespace: `FsToolkit.ErrorHandling` + +Function Signature: + +```fsharp +Async<'a> option -> Async<'a option> +``` + +Note that `sequence` is the same as `traverse id`. See also [Option.traverseAsync](traverseAsync.md). + +See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). + +## Examples + +### Example 1 + +```fsharp +let a1 : Async = + Option.sequenceAsync (Some (Async.singleton 42)) +// async { return Some 42 } + +let a2 : Async = + Option.sequenceAsync None +// async { return None } +``` diff --git a/gitbook/option/traverseAsync.md b/gitbook/option/traverseAsync.md new file mode 100644 index 00000000..6cde92da --- /dev/null +++ b/gitbook/option/traverseAsync.md @@ -0,0 +1,45 @@ +## Option.traverseAsync + +Namespace: `FsToolkit.ErrorHandling` + +Function Signature: + +```fsharp +('a -> Async<'b>) -> 'a option -> Async<'b option> +``` + +Note that `traverse` is the same as `map >> sequence`. See also [Option.sequenceAsync](sequenceAsync.md). + +See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). + +## Examples + +### Example 1 + +Let's assume we have a type `Customer`: + +```fsharp +type Customer = { + Id : int + Email : string +} +``` + +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: + +```fsharp +// string -> Async +let getCustomerByEmail email : Async = async { + return { Id = 1; Email = "test@test.com" } // return a constant for simplicity +} +``` + +If we have a value of type `string option` and want to call the `getCustomerByEmail` function, we can achieve it using the `traverseAsync` function as below: + +```fsharp +Some "test@test.com" |> Option.traverseAsync getCustomerByEmail +// async { return Some { Id = 1; Email = "test@test.com" } } + +None |> Option.traverseAsync getCustomerByEmail +// async { return None } +``` diff --git a/src/FsToolkit.ErrorHandling/Option.fs b/src/FsToolkit.ErrorHandling/Option.fs index 5ab7d473..32a8066a 100644 --- a/src/FsToolkit.ErrorHandling/Option.fs +++ b/src/FsToolkit.ErrorHandling/Option.fs @@ -321,6 +321,28 @@ module Option = opt + /// Converts a Option> to an Async> + let inline sequenceAsync (optAsync: Option>) : Async> = + async { + match optAsync with + | Some asnc -> + let! x = asnc + return Some x + | None -> return None + } + + /// + /// Maps an Async function over an Option, returning an Async Option. + /// + /// The function to map over the Option. + /// The Option to map over. + /// An Async Option with the mapped value. + let inline traverseAsync + ([] f: 'T -> Async<'T>) + (opt: Option<'T>) + : Async> = + sequenceAsync ((map f) opt) + /// /// Creates an option from a boolean value and a value of type 'a. /// If the boolean value is true, returns Some value. diff --git a/tests/FsToolkit.ErrorHandling.Tests/Option.fs b/tests/FsToolkit.ErrorHandling.Tests/Option.fs index 219566a4..e4ed03ce 100644 --- a/tests/FsToolkit.ErrorHandling.Tests/Option.fs +++ b/tests/FsToolkit.ErrorHandling.Tests/Option.fs @@ -184,6 +184,66 @@ let teeIfTests = Expect.equal foo "foo" "" ] +let sequenceAsyncTests = + testList "Option.sequenceAsync Tests" [ + testCaseAsync "sequenceAsync returns the async value if Some" + <| async { + let optAsync = + async { return "foo" } + |> Some + + let! value = + optAsync + |> Option.sequenceAsync + + Expect.equal value (Some "foo") "" + } + + testCaseAsync "sequenceAsync returns None if None" + <| async { + let optAsync = None + + let! value = + optAsync + |> Option.sequenceAsync + + Expect.equal value None "" + } + ] + +let traverseAsyncTests = + testList "Option.traverseAsync Tests" [ + testCaseAsync "traverseAsync returns the async value if Some" + <| async { + let optAsync = Some "foo" + + let optFunc = + id + >> Async.singleton + + let! value = + (optFunc, optAsync) + ||> Option.traverseAsync + + Expect.equal value (Some "foo") "" + } + + testCaseAsync "traverseAsync returns None if None" + <| async { + let optAsync = None + + let optFunc = + id + >> Async.singleton + + let! value = + (optFunc, optAsync) + ||> Option.traverseAsync + + Expect.equal value None "" + } + ] + let traverseResultTests = testList "Option.traverseResult Tests" [ testCase "traverseResult with Some of valid data" @@ -366,6 +426,8 @@ let optionOperatorsTests = let allTests = testList "Option Tests" [ + sequenceAsyncTests + traverseAsyncTests traverseResultTests tryParseTests tryGetValueTests