Created
April 12, 2021 02:24
-
-
Save michaeloyer/db9bb3e5f32903753f07907be9b85465 to your computer and use it in GitHub Desktop.
Playing around with F# Computation Expressions (Result, Option, and AsyncResult). I think I get monads! Woo!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Dummy functions to simulate a function that return a result | |
let getNumber expected resultNumber number = | |
if number = expected then | |
Ok resultNumber | |
else | |
Error (sprintf $"Expected {expected} but got {number}") | |
let getOne = getNumber 0 1 | |
let getTwo = getNumber 1 2 | |
let getThree = getNumber 2 3 | |
let getFour = getNumber 3 4 | |
let getFive = getNumber 4 5 | |
// # Pyramid of Doom | |
match getOne 0 with | |
| Ok one -> | |
match getTwo one with | |
| Ok two -> | |
match getThree two with | |
| Ok three -> | |
match getFour three with | |
| Ok four -> | |
match getFive four with | |
| Ok five -> Ok (one + two + three + four + five) | |
| Error message -> Error message | |
| Error message -> Error message | |
| Error message -> Error message | |
| Error message -> Error message | |
| Error message -> Error message | |
|> function | |
| Ok number -> printfn $"Success!: {number}" | |
| Error message -> printfn $"Error!: {message}" | |
// Result.bind in disguise | |
let getNewResultWithOkResultValue func result = | |
match result with | |
| Ok value -> func value | |
| Error message -> Error message | |
getOne 0 | |
|> getNewResultWithOkResultValue getTwo | |
|> getNewResultWithOkResultValue getThree | |
|> Result.bind getFour | |
|> Result.bind getFive | |
|> function | |
| Ok number -> printfn $"Success!: {number}" | |
| Error message -> printfn $"Error!: {message}" | |
// Computation Expression! | |
type ResultBuilder() = | |
member _.Bind(result, f) = | |
Result.bind f result | |
member _.Return(value) = | |
Ok value | |
member _.ReturnFrom(result) = result | |
let result = ResultBuilder() | |
// Take it for a spin | |
result { | |
let! one = getOne 0 | |
let! two = getTwo one | |
let! three = getThree two | |
let! four = getFour three | |
let! five = getFive four | |
return one + two + three + four + five | |
} | |
|> function | |
| Ok number -> printfn $"Success!: {number}" | |
| Error message -> printfn $"Error!: {message}" | |
// Option Dummy Functions | |
let maybeNumber expected resultNumber = | |
getNumber expected resultNumber | |
>> function | |
| Ok value -> Some value | |
| Error _ -> None | |
let maybeOne = maybeNumber 0 1 | |
let maybeTwo = maybeNumber 1 2 | |
let maybeThree = maybeNumber 2 3 | |
// Computation Expression! | |
type OptionBuilder() = | |
member _.Bind(optional, f) = Option.bind f optional | |
member _.Return(value) = Some value | |
let option = OptionBuilder() | |
// Taking it for a spin | |
option { | |
let! one = maybeOne 0 | |
let! two = maybeTwo one | |
let! three = maybeThree two | |
return one + two + three | |
} | |
|> function | |
| Some value -> printfn $"Success!: {value}" | |
| None -> printfn "Error! Missing Value :(" | |
// Just for fun | |
module Result = | |
let ofOption option = | |
match option with | |
| Some value -> Ok value | |
| None -> Error () | |
// Making a function in the result context | |
let useOneTwoThreePipeline number = | |
result { | |
let! one = getOne number | |
let! two = maybeTwo one | |
|> Result.ofOption | |
|> Result.mapError (fun () -> "maybeTwo Failed") | |
return! getThree two | |
} | |
useOneTwoThreePipeline 0 | |
|> function | |
| Ok number -> printfn $"Success!: {number}" | |
| Error message -> printfn $"Error!: {message}" | |
module Async = | |
// Probably a little pointless | |
let apply value = async { return value } | |
//A little crazy that map and bind are one character off from each other | |
let map f asyncValue = async { | |
let! value = asyncValue | |
return (f value) | |
} | |
let bind f asyncValue = async { | |
let! value = asyncValue | |
return! (f value) | |
} | |
type AsyncResult<'T, 'TError> = Async<Result<'T, 'TError>> | |
module AsyncResult = | |
let apply value = async { return (Ok value) } | |
let applyResult (result:Result<_,_>) = async { return result } | |
let map f asyncResult = | |
async { | |
let! result = asyncResult; | |
return (result |> Result.map f) | |
} | |
let mapError f asyncResult = | |
async { | |
let! result = asyncResult; | |
return (result |> Result.mapError f) | |
} | |
//The only part that tripped me up this time around of practice, Had to look it up: | |
//https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/c551c3c0ed21d2d7e1cb3ab6628567de9ccf11f3/src/FsToolkit.ErrorHandling/AsyncResult.fs#L18 | |
let bind f asyncResult = | |
async { | |
let! result = asyncResult | |
return! | |
match result with | |
| Ok value -> f value | |
| error -> Async.apply error | |
} | |
type AsyncResultBuilder() = | |
member _.Bind(asyncResult, f) = AsyncResult.bind f asyncResult | |
member _.Return(value) = AsyncResult.apply value | |
let asyncResult = AsyncResultBuilder() | |
let theAsyncResult = | |
asyncResult { | |
let! one = AsyncResult.apply 1 | |
let! two = AsyncResult.apply 2 | |
let! three = Async.apply (Ok 3) | |
let! four = Async.apply (Error "Never been so excited to see an error!") | |
return one + two + three + four | |
} | |
theAsyncResult | |
|> Async.RunSynchronously | |
|> function | |
| Ok v -> printfn "AsyncResult Success: %i" v | |
| Error error -> printfn "AsyncResult Error: %s" error |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment