Skip to content

Instantly share code, notes, and snippets.

@jeremyabbott
Created April 9, 2018 19:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyabbott/9a1aade4d6ae46dee1291bad909019bd to your computer and use it in GitHub Desktop.
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
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 "jeremy@test.com"
// 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