Created
April 9, 2018 19:24
-
-
Save jeremyabbott/9a1aade4d6ae46dee1291bad909019bd to your computer and use it in GitHub Desktop.
Long script with code from F# for fun and Profit Explaining how `let` works along with details on how CEs work
This file contains hidden or 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
let log p = printfn "expression is %A" p | |
let loggedWorkflow = | |
let x = 42 | |
log x | |
let y = 43 | |
log y | |
let z = x + y // block following let is unfinished. Every expression must return something. | |
log z | |
//return | |
z | |
// Logging as a side effect | |
type LoggingBuilder() = | |
let log p = printfn "expression is %A" p | |
member __.Bind(x, f) = | |
log x | |
f x | |
member __.Return(x) = | |
x | |
let logger = new LoggingBuilder() | |
let loggedWorkflow' = | |
logger { | |
let! x = 42 // notice that logging is handled for us. | |
let! y = 43 | |
let! z = x + y | |
return z | |
} | |
// Using CEs to deal with "wrapper types" | |
let divideBy bottom top = | |
if bottom = 0 | |
then None | |
else Some(top/bottom) | |
divideBy 0 5 // None | |
divideBy 5 25 | |
let divideByWorkflow init x y z = // yuck | |
let a = init |> divideBy x | |
match a with | |
| None -> None // give up | |
| Some a' -> // keep going | |
let b = a' |> divideBy y | |
match b with | |
| None -> None // give up | |
| Some b' -> // keep going | |
let c = b' |> divideBy z | |
match c with | |
| None -> None // give up | |
| Some c' -> // keep going | |
//return | |
Some c' | |
let good = divideByWorkflow 12 3 2 1 | |
let bad = divideByWorkflow 12 3 0 1 | |
type MaybeBuilder() = | |
member __.Bind(x, f) = | |
match x with | |
| None -> None | |
| Some a -> f a | |
member __.Return(x) = | |
Some x | |
let maybe = new MaybeBuilder() | |
let divideByWorkflow' init x y z = | |
maybe { | |
let! a = init |> divideBy x | |
let! b = a |> divideBy y | |
let! c = b |> divideBy z | |
return c | |
} | |
// Return! vs. Return | |
type OrElseBuilder() = | |
member __.ReturnFrom(x) = x // return the underlying type, not the wrapped one. | |
member __.Combine (a,b) = | |
match a with | |
| Some _ -> a // a succeeds -- use it | |
| None -> b // a fails -- use b instead | |
member __.Delay(f) = f() | |
let orElse = new OrElseBuilder() | |
let map1 = [ ("1","One"); ("2","Two") ] |> Map.ofList | |
let map2 = [ ("A","Alice"); ("B","Bob") ] |> Map.ofList | |
let map3 = [ ("CA","California"); ("NY","New York") ] |> Map.ofList | |
let multiLookup key = orElse { | |
return! map1.TryFind key | |
return! map2.TryFind key | |
return! map3.TryFind key | |
} | |
multiLookup "A" |> printfn "Result for A is %A" | |
multiLookup "CA" |> printfn "Result for CA is %A" | |
multiLookup "X" |> printfn "Result for X is %A" | |
// let vs let! | |
// called function always decides what to do in imperative code | |
type EmailAddress = EmailAddress of string | |
let createEmailAddressWithContinuations success failure (s:string) = | |
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$") | |
then success (EmailAddress s) | |
else failure "Email address must contain an @ sign" | |
// continuations: when success do x with the emailAddress, when failure, do y | |
// e.g. | |
let errorMessage message = message |> Error | |
// partially apply function | |
let validateEmail = createEmailAddressWithContinuations Ok errorMessage | |
// valid email | |
validateEmail "[email protected]" | |
// invalid email | |
validateEmail "jeremy" | |
// Continuation Passing Style (CPS) | |
// every function is called with an extra “what to do next” function parameter. | |
// Imperative style | |
// | |
// call a function -> | |
// <- return from the function | |
// call another function -> | |
// <- return from the function | |
// call yet another function -> | |
// <- return from the function | |
// Continuation Style | |
// | |
// evaluate something and pass it into -> | |
// a function that evaluates something and passes it into -> | |
// another function that evaluates something and passes it into -> | |
// yet another function that evaluates something and passes it into -> | |
// ...etc... | |
// continuation style results in a control-flow pipeline. | |
// imperative style requires a "master-controller" to handle the control-flow of each call. | |
// when let isn't at the top level, it cannot exist in isolation. | |
// this is okay | |
let okay a b = | |
let c = sprintf "%s %s" a b // let c = someExpression | |
c // c = someExpression in [an expression involving c] | |
// whenever c occurs after "let c", it's replaced with the value of c | |
let notOkay a b = | |
let c = sprintf "%s %s" a b // c is never used in a subsequent expression | |
(* When we write the following: | |
let x = 42 | |
let y = 43 | |
let z = x + y | |
This is what happens: | |
*) | |
let x = 42 in // used in sequence expressions, and to separate expressions from bindings. | |
let y = 43 in | |
let z = x + y in | |
z // the result | |
(* what if x was defined this way? | |
fun x -> [an expression involving x] | |
someExpression |> (fun x -> [an expression involving x] ) | |
*) | |
42 |> (fun x -> | |
43 |> (fun y -> | |
x + y |> (fun z -> // expression involving x which is 42 | |
z))) | |
// we've replaced let with its continuation style equivalent | |
let pipeInto (someExpression,lambda) = | |
someExpression |> lambda | |
pipeInto (42, fun x -> | |
pipeInto (43, fun y -> | |
pipeInto (x + y, fun z -> | |
z))) | |
pipeInto (42, fun x -> // let x = 42 | |
pipeInto (43, fun y -> // let y = 43 | |
pipeInto (x + y, fun z -> // let z = x + y | |
z))) | |
let pipeIntoAndLog (someExpression,lambda) = | |
printfn "expression is %A" someExpression | |
someExpression |> lambda | |
pipeIntoAndLog (42, fun x -> // let x = 42 | |
pipeIntoAndLog (43, fun y -> // let y = 43 | |
pipeIntoAndLog (x + y, fun z -> // let z = x + y | |
z))) | |
Result.bind | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment