Skip to content

Instantly share code, notes, and snippets.

@michaeloyer
Created April 12, 2021 02:24
Show Gist options
  • Save michaeloyer/db9bb3e5f32903753f07907be9b85465 to your computer and use it in GitHub Desktop.
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!
// 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