diff --git a/gitbook/option/sequenceAsyncResult.md b/gitbook/option/sequenceAsyncResult.md new file mode 100644 index 00000000..58a45a46 --- /dev/null +++ b/gitbook/option/sequenceAsyncResult.md @@ -0,0 +1,31 @@ +## Option.sequenceAsyncResult + +Namespace: `FsToolkit.ErrorHandling` + +Function Signature: + +```fsharp +Async> option -> Async, 'e> +``` + +Note that `sequence` is the same as `traverse id`. See also [Option.traverseAsyncResult](traverseAsyncResult.md). + +See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). + +## Examples + +### Example 1 + +```fsharp +let r1 : Async> = + Some (async { return Ok 42 }) |> Option.sequenceAsyncResult +// async { return Ok (Some 42) } + +let r2 : Async> = + Some (async { return Error "something went wrong" }) |> Option.sequenceAsyncResult +// async { return Error "something went wrong" } + +let r3 : Async> = + None |> Option.sequenceAsyncResult +// async { return Ok None } +``` diff --git a/gitbook/option/traverseAsyncResult.md b/gitbook/option/traverseAsyncResult.md new file mode 100644 index 00000000..b0f73d6f --- /dev/null +++ b/gitbook/option/traverseAsyncResult.md @@ -0,0 +1,44 @@ +## Option.traverseAsyncResult + +Namespace: `FsToolkit.ErrorHandling` + +Function Signature: + +```fsharp +('a -> Async>) -> 'a option -> Async> +``` + +Note that `traverse` is the same as `map >> sequence`. See also [Option.sequenceAsyncResult](sequenceAsyncResult.md). + +See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). + +## Examples + +### Example 1 + +Say we have a function to get a number from a database (asynchronously), and multiply our input by that number if it's found: + +```fsharp +let tryMultiplyWithDatabaseValue: float -> Async> = // ... +``` + +If we start with an optional vaue, then we could map this funciton using `Option.traverseAsyncResult` as follows: + +```fsharp +let input = Some 1.234 + +input // float option +|> Option.traverseAsyncResult tryMultiplyWithDatabaseValue // Async> +``` + +If we combine this with the [AsyncResult computation expression](../asyncResult/ce.md), we could directly `let!` the output: + +```fsharp +asyncResult { + let input = Some 1.234 + + let! output = // float option + input // float option + |> Option.traverseAsyncResult tryMultiplyWithDatabaseValue // Async> +} +``` diff --git a/src/FsToolkit.ErrorHandling/Option.fs b/src/FsToolkit.ErrorHandling/Option.fs index 50495d26..4b3e15c0 100644 --- a/src/FsToolkit.ErrorHandling/Option.fs +++ b/src/FsToolkit.ErrorHandling/Option.fs @@ -321,7 +321,11 @@ module Option = opt + /// /// Converts a Option> to an Async> + /// + /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/sequenceasync + /// let inline sequenceAsync (optAsync: Option>) : Async> = async { match optAsync with @@ -333,6 +337,8 @@ module Option = /// /// Maps an Async function over an Option, returning an Async Option. + /// + /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/traverseasync /// /// The function to map over the Option. /// The Option to map over. @@ -343,6 +349,39 @@ module Option = : Async> = sequenceAsync ((map f) opt) + /// + /// Converts a Async> option to an Async> + /// + /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/sequenceasyncresult + /// + let inline sequenceAsyncResult + (optAsyncResult: Async> option) + : Async> = + async { + match optAsyncResult with + | Some asncRes -> + let! xRes = asncRes + + return + xRes + |> Result.map Some + | None -> return Ok None + } + + /// + /// Maps an AsyncResult function over an option, returning an AsyncResult option. + /// + /// /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/option/traverseasyncresult + /// + /// The function to map over the Option. + /// The Option to map over. + /// An AsyncResult Option with the mapped value. + let inline traverseAsyncResult + ([] f: 'T -> Async>) + (opt: 'T option) + : Async> = + sequenceAsyncResult ((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 e4ed03ce..0eaaad4f 100644 --- a/tests/FsToolkit.ErrorHandling.Tests/Option.fs +++ b/tests/FsToolkit.ErrorHandling.Tests/Option.fs @@ -211,6 +211,49 @@ let sequenceAsyncTests = } ] +let sequenceAsyncResultTests = + testList "Option.sequenceAsyncResult Tests" [ + testCaseAsync "sequenceAsyncResult returns the async Ok value if Some" + <| async { + let optAsyncOk = + async { return Ok "foo" } + |> Some + + let! valueRes = + optAsyncOk + |> Option.sequenceAsyncResult + + let value = Expect.wantOk valueRes "Expect to get back OK" + Expect.equal value (Some "foo") "Expect to get back value" + } + + testCaseAsync "sequenceAsyncResult returns the async Error value if Some" + <| async { + let optAsyncOk = + async { return Error "error" } + |> Some + + let! valueRes = + optAsyncOk + |> Option.sequenceAsyncResult + + let errorValue = Expect.wantError valueRes "Expect to get back Error" + Expect.equal errorValue "error" "Expect to get back the error value" + } + + testCaseAsync "sequenceAsyncResult returns None if None" + <| async { + let optAsyncNone = None + + let! valueRes = + optAsyncNone + |> Option.sequenceAsyncResult + + let valueNone = Expect.wantOk valueRes "Expect to get back OK" + Expect.isNone valueNone "Expect to get back None" + } + ] + let traverseAsyncTests = testList "Option.traverseAsync Tests" [ testCaseAsync "traverseAsync returns the async value if Some" @@ -259,6 +302,60 @@ let traverseResultTests = |> Expect.hasOkValue (Some validLng) ] +let traverseAsyncResultTests = + testList "Option.traverseAsyncResult Tests" [ + testCaseAsync "traverseAsyncResult with valid latitute data" + <| async { + let tryCreateLatAsync = fun l -> async { return Latitude.TryCreate l } + + let! valueRes = + Some lat + |> Option.traverseAsyncResult tryCreateLatAsync + + let value = Expect.wantOk valueRes "Expect to get OK" + Expect.equal value (Some validLat) "Expect to get valid latitute" + } + + testCaseAsync "traverseAsyncResult id returns async Ok value if Some" + <| async { + let optAsyncOk = + async { return Ok "foo" } + |> Some + + let! valueRes = + optAsyncOk + |> Option.traverseAsyncResult id + + let value = Expect.wantOk valueRes "Expect to get back OK" + Expect.equal value (Some "foo") "Expect to get back value" + } + + testCaseAsync "traverseAsyncResult id returns the async Error value if Some" + <| async { + let optAsyncOk = + async { return Error "error" } + |> Some + + let! valueRes = + optAsyncOk + |> Option.traverseAsyncResult id + + let errorValue = Expect.wantError valueRes "Expect to get back Error" + Expect.equal errorValue "error" "Expect to get back the error value" + } + + testCaseAsync "traverseAsyncResult id returns None if None" + <| async { + let optAsyncNone = None + + let! valueRes = + optAsyncNone + |> Option.traverseAsyncResult id + + let valueNone = Expect.wantOk valueRes "Expect to get back OK" + Expect.isNone valueNone "Expect to get back None" + } + ] let tryParseTests = testList "Option.tryParse" [ @@ -427,8 +524,10 @@ let optionOperatorsTests = let allTests = testList "Option Tests" [ sequenceAsyncTests + sequenceAsyncResultTests traverseAsyncTests traverseResultTests + traverseAsyncResultTests tryParseTests tryGetValueTests ofResultTests