Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Thirteen ways of
looking at a turtle
@ScottWlaschin
fsharpforfunandprofit.com/turtle
Actor model Error handling
Interpreter Capabilities
A taste of many different approaches
A taste of many different approaches
This is a crazy experiment:
~4 mins per topic!
See also fsharpforfunandprofit.com/fpp...
Turtle graphics in action
Turtle graphics in action
Turtle graphics in action
Turtle API
API Description
Move aDistance Move some distance in the current
direction.
Turn anAngle Turn a certain number ...
1. Object OrientedTurtle
Data and behavior are combined
A "Tootle"
Overview
call
Turtle Classcall
Contains mutable
state
Client
type Turtle() =
let mutable currentPosition = initialPosition
let mutable currentAngle = 0.0<Degrees>
let mutable currentP...
member this.Move(distance) =
Logger.info (sprintf "Move %0.1f" distance)
let startPos = currentPosition
// calculate new p...
member this.Turn(angleToTurn) =
Logger.info (sprintf "Turn %0.1f" angleToTurn)
// calculate new angle
let newAngle =
(curr...
member this.PenUp() =
Logger.info "Pen up"
currentPenState <- Up
member this.PenDown() =
Logger.info "Pen down"
currentPen...
Usage example
let drawTriangle() =
let distance = 50.0
let turtle =Turtle()
turtle.Move distance
turtle.Turn 120.0<Degrees...
OO Turtle demo
Advantages and disadvantages
• Advantages
– Familiar
• Disadvantages
– Stateful/Black box
– Can't easily compose
– Hard-co...
2.Abstract DataTurtle
Data is separated from behavior
type TurtleState = private {
mutable position : Position
mutable angle : float<Degrees>
mutable penState : PenState
}
modu...
Usage example
let drawTriangle() =
let distance = 50.0
let turtle = Turtle.create()
Turtle.move distance turtle
Turtle.tur...
Advantages and disadvantages
• Advantages
– Simple
– Forces composition over inheritance!
• Disadvantages
– As with OO: st...
3. FunctionalTurtle
Data is immutable
Overview
Client Turtle
Functions
state
new state
Uses immutable
state
state
new state
type TurtleState = {
position : Position
angle : float<Degrees>
penState : PenState
}
module Turtle =
let move distance st...
Usage example
let drawTriangle() =
let s0 =Turtle.initialTurtleState
let s1 =Turtle.move 50.0 s0
let s2 =Turtle.turn 120.0...
Piping
TurtleState Turtle
function
new TurtleState
TurtleState Turtle
function
new TurtleState
Usage example with pipes
let drawTriangle() =
Turtle.initialTurtleState
|>Turtle.move 50.0
|>Turtle.turn 120.0<Degrees>
|>...
Advantages and disadvantages
• Advantages
– Immutability: Easy to reason about
– Stateless: Easy to test
– Functions are c...
4. State monad
Threading state behind the scenes
Turtle Canvas
Turtle Canvas
Change implementation so that Move returns a pair:
* New state, and
* Actual distance moved
Usage example
let s0 = Turtle.initialTurtleState
let (actualDistA,s1) = Turtle.move 80.0 s0
if actualDistA < 80.0 then
pri...
Transforming the function #1
TurtleState
Turtle
function
new TurtleState
Input Output
Transforming the function #2
Currying
TurtleStateinput
new TurtleState
Output
Transforming the function #3
A function returns
another function
Input
Turtle
function
Transforming the function #4
Wrap this in a type
called "State"
For more on how this works, see fsharpforfunandprofit.com/...
Usage example
let stateExpression = state { // "state" expression
let! distA = move 80.0
if distA < 80.0 then
printfn "fir...
Advantages and disadvantages
• Advantages
– Looks imperative, but preserves immutability.
– Functions still composable
• D...
5. Error handling
How to return errors?
type Result<'successInfo,'errorInfo> =
| Success of 'successInfo
| Failure of 'errorInfo
TurtleState...
let move distanceRequested state =
// calculate new position
// draw line if needed
if actualDistanceMoved <> distanceRequ...
Usage example
let s0 = Turtle.initialTurtleState
let result1 = s0 |> Turtle.move 80.0
match result1 with
| Success s1 ->
l...
Usage example
let finalResult = result { // result expression
let s0 = Turtle.initialTurtleState
let! s1 = s0 |> Turtle.mo...
Usage example
let finalResult = resultState { // "result" and "state" combined
do! Turtle.move 80.0
printfn "first move su...
Advantages and disadvantages
• Advantages
– Errors are explicitly returned (no exceptions)
– Looks like "happy path" code
...
5½. Async turtle
What if the Turtle calls were async?
What if the Turtle calls were async?
let s0 = Turtle.initialTurtleState
s0 |> Turtle. moveAsync 80.0 (fun s1 ->
s1 |> Turt...
Async usage example
let finalResult = async { // "async" expression
let s0 =Turtle.initialTurtleState
let! s1 = s0 |> Turt...
Review: Common patterns
• Composition
– Chaining functions together
• Explicitness
– Explicit state management (no mutatio...
6. Batch commands
How can the caller avoid managing
state?
Overview
Turtle
Functions
Batch
Runner
Client
state
new staterun
[command,
command,
command,
command] Loop over
commands
Overview
Keep track of state here
Turtle
Functions
Batch
Runner
[command,
command,
command,
command]
Client
state
new stat...
How to convert a function into data?
// Turtle functions
let move distance = ...
let turn angle = ...
let penUp () = ...
l...
Usage example
// create the list of commands
let commands = [
Move 100.0
Turn 120.0<Degrees>
Move 100.0
Turn 120.0<Degrees...
"execute" implementation
/// Apply a command to the turtle state and return the new state
let executeCommand state command...
"run" implementation
/// Run list of commands in one go
let run aListOfCommands =
let mutable state = Turtle.initialTurtle...
"run" implementation
/// Run list of commands in one go
let run aListOfCommands =
aListOfCommands
|> List.fold executeComm...
Advantages and disadvantages
• Advantages
– Decoupled
– Simpler than monads!
• Disadvantages
– Batch oriented only
– No co...
7.Actor model
Turtle
Functions
ActorcommandClient
state
new state
queue
message
processing
loop
Turtle
Functions
ActorcommandClient
queue
message
processing
loop
Keep track of state here
state
new state
Pulling commands off a queue
let rec loop turtleState =
let command = // read a command from the message queue
let newStat...
Usage example
// post a list of commands
let turtleActor = newTurtleActor()
turtleActor.Post (Move 100.0)
turtleActor.Post...
Advantages and disadvantages
• Advantages
– Decoupled
– Simpler than state monad
• Disadvantages
– Extra boilerplate needed
8. Event Sourcing
How should we persist state?
Overview
Turtle
Functions
Command
Handler
command
Client
state
new state
Recreate state
Event Store
Generate events
write
...
Overview
Turtle
Functions
Command
Handler
command
Client
state
new state
Recreate state
Event Store
Generate events
write
...
Overview
Turtle
Functions
Command
Handler
command
Client
state
new state
Recreate state
Event Store
Generate events
write
...
Command vs. Event
typeTurtleCommand =
| Move of Distance
|Turn of Angle
| PenUp
| PenDown
What you *want* to have happen
Command vs. Event
typeTurtleEvent =
| Moved of Distance * StartPosition * EndPosition
|Turned of AngleTurned * FinalAngle
...
Implementation
/// Apply an event to the current state and return the new state
let applyEvent state event =
match event w...
Implementation
let handleCommand (command:TurtleCommand) =
// First load all the events from the event store
let eventHist...
Advantages and disadvantages
• Advantages
– Decoupled
– Stateless
– Supports replay of events
• Disadvantages
– More compl...
9. Stream Processing
Overview
Stream processor
Stream processor
Command
Handler
commands
event
stream
new events
Select, Filter
Transform
event...
Overview
Stream processor
Stream processor
Command
Handler
commands
event
stream
new events
Select, Filter
Transform
event...
Overview
Stream processor
Stream processor
Command
Handler
commands
event
stream
new events
Select, Filter
Transform
event...
Turtle stream processing example
TurtleState processor
Command
Handler
command
Event Store
Turtle stream processing example
TurtleState processor
Audit processor
Command
Handler
event
streamcommand Canvas processo...
Stream processing demo
Auditing Processor
Canvas Processor
DistanceTravelled Processor
Advantages and disadvantages
• Advantages
– Same as Event Sourcing, plus
– Separates state logic from business logic
– Mic...
Review
• "Conscious decoupling" with data types
– Passing data instead of calling functions
• Immutable data stores
– Stor...
10. OO style
dependency injection
Overview
Turtle Class
Move
ICanvas ILogger
Inject
Turn
PenUp
Same for the Turtle client
Client
DrawTriangle
ITurtle
Inject
DrawSquare
OO dependency injection demo
Advantages and disadvantages
• Advantages
– Well understood
• Disadvantages
– Unintentional dependencies
– Interfaces ofte...
11. FP style
dependency injection
Overview
Turtle Module
Move drawlogInfo
Turn logInfo
PenUp logInfo
Overview
MovelogInfo draw Distance Output
Overview
logInfo draw Distance Output
Partial
Application
Overview
Move
logInfo draw
Distance Output
Dependencies
are "baked in"
"move" implementation
let move logInfo draw distance state =
logInfo (sprintf "Move %0.1f" distance)
// calculate new posi...
"turn" implementation
let turn logInfo angleToTurn state =
logInfo (sprintf "Turn %0.1f" angleToTurn)
// calculate new ang...
Turtle.initialTurtleState
|> move 50.0
|> turn 120.0<Degrees>
Partial application in practice
let move =Turtle.move Logger...
FP dependency injection demo
Advantages and disadvantages
• Advantages
– Dependencies are explicit
– Functions, not interfaces
– Counterforce to having...
12. Interpreter
APIs create coupling
module TurtleAPI =
move : Distance -> State -> Distance * State
turn : Angle -> State -> State
penUp ...
APIs create coupling
module TurtleAPI =
move : Distance -> State -> Result<Distance * State>
turn : Angle -> State -> Resu...
APIs create coupling
module TurtleAPI =
move : Distance -> State -> AsyncResult<Distance * State>
turn : Angle -> State ->...
Overview
Move response
Interpreter"Program" data (e.g. Move)Client
Turn response
Program data (e.g. Turn)
Program
etc ...
type TurtleProgram =
// (input params) (response)
| Move of Distance * (Distance -> TurtleProgram)
| Turn of Angle * (unit...
Usage example
let drawTriangle =
Move (100.0, fun actualDistA ->
// actualDistA is response from interpreter
Turn (120.0<D...
Usage example
let drawTriangle = turtleProgram { // Seen this trick before!
let! actualDistA = move 100.0
do! turn 120.0<D...
Overview
TurtleProgram
A set of
instructions
Interpreter A
Interpreter B
Interpreter C
Example:Turtle Interpreter
let rec interpretAsTurtle state program =
match program with
| Stop ->
state
| Move (dist, next...
Example: Distance Interpreter
let rec interpretAsDistance distanceSoFar program =
match program with
| Stop ->
distanceSoF...
Interpreter demo
Advantages and disadvantages
• Advantages
– Completely decoupled
– Pure API
– Optimization possible
• Disadvantages
– Comp...
13. Capabilities
Overview
APIcallClient
call
I'm sorry,
Dave, I'm afraid
I can't do that
Rather than telling me what I can't do,
why not tell me what I can do?
Capability-based API
Overview
APIcall
Some
capabilities
Client
Overview
APIcallClient
Use a capability
Turtle API capabilities
Turtle APIMoveClient
Move,
Turn,
PenDown
Move
Move,
Turn,
PenDown
OK
Hit the edge
Turtle Capabilities
type TurtleCapabilities = {
move : MoveFn option
turn : TurnFn
penUp : PenUpDownFn
penDown: PenUpDownF...
Usage example
let turtleCaps = Turtle.start() // initial set of capabilities
match turtleCaps.move with
| None ->
warn "Er...
Capabilities demo
Advantages and disadvantages
• Advantages
– Client doesn't need to duplicate business logic
– Better security
– Capabiliti...
Slides and video here
F# consulting
Phew!
fsharpforfunandprofit.com/turtle
Upcoming SlideShare
Loading in …5
×

Thirteen ways of looking at a turtle

3,850 views

Published on

(Video and code available at http://fsharpforfunandprofit.com/turtle)

In this fast-paced talk, I'll start with the well known API for turtle graphics, and then stretch it to the breaking point by creating 13 different implementations, each demonstrating a different functional programming technique.

Along the way, we'll use partial application, functional dependency injection, validation
with Success/Failure results, the Either monad, the State monad, agents with message queues, event sourcing, stream processing, capability-based design, and the interpreter pattern (aka free monad). Phew!

Published in: Software

Thirteen ways of looking at a turtle

  1. 1. Thirteen ways of looking at a turtle @ScottWlaschin fsharpforfunandprofit.com/turtle
  2. 2. Actor model Error handling Interpreter Capabilities A taste of many different approaches
  3. 3. A taste of many different approaches This is a crazy experiment: ~4 mins per topic! See also fsharpforfunandprofit.com/fppatterns
  4. 4. Turtle graphics in action
  5. 5. Turtle graphics in action
  6. 6. Turtle graphics in action
  7. 7. Turtle API API Description Move aDistance Move some distance in the current direction. Turn anAngle Turn a certain number of degrees clockwise or anticlockwise. PenUp PenDown Put the pen down or up. When the pen is down, moving the turtle draws a line. All of the following implementations will be based on this interface or some variant of it.
  8. 8. 1. Object OrientedTurtle Data and behavior are combined A "Tootle"
  9. 9. Overview call Turtle Classcall Contains mutable state Client
  10. 10. type Turtle() = let mutable currentPosition = initialPosition let mutable currentAngle = 0.0<Degrees> let mutable currentPenState = initialPenState "mutable" keyword needed in F# Units of measure used to document API
  11. 11. member this.Move(distance) = Logger.info (sprintf "Move %0.1f" distance) let startPos = currentPosition // calculate new position let endPos = calcNewPosition distance currentAngle startPos // draw line if needed if currentPenState = Down then Canvas.draw startPos endPos // update the state currentPosition <- endPos Assignment
  12. 12. member this.Turn(angleToTurn) = Logger.info (sprintf "Turn %0.1f" angleToTurn) // calculate new angle let newAngle = (currentAngle + angleToTurn) % 360.0<Degrees> // update the state currentAngle <- newAngle
  13. 13. member this.PenUp() = Logger.info "Pen up" currentPenState <- Up member this.PenDown() = Logger.info "Pen down" currentPenState <- Down
  14. 14. Usage example let drawTriangle() = let distance = 50.0 let turtle =Turtle() turtle.Move distance turtle.Turn 120.0<Degrees> turtle.Move distance turtle.Turn 120.0<Degrees> turtle.Move distance turtle.Turn 120.0<Degrees> // back home at (0,0) with angle 0
  15. 15. OO Turtle demo
  16. 16. Advantages and disadvantages • Advantages – Familiar • Disadvantages – Stateful/Black box – Can't easily compose – Hard-coded dependencies (for now)
  17. 17. 2.Abstract DataTurtle Data is separated from behavior
  18. 18. type TurtleState = private { mutable position : Position mutable angle : float<Degrees> mutable penState : PenState } module Turtle = let move distance state = ... let turn angleToTurn state = ... let penUp state = ... let penDown log state = Only turtle functions can access it State passed in explicitly Data Behavior
  19. 19. Usage example let drawTriangle() = let distance = 50.0 let turtle = Turtle.create() Turtle.move distance turtle Turtle.turn 120.0<Degrees> turtle Turtle.move distance turtle Turtle.turn 120.0<Degrees> turtle Turtle.move distance turtle Turtle.turn 120.0<Degrees> turtle State passed in explicitly
  20. 20. Advantages and disadvantages • Advantages – Simple – Forces composition over inheritance! • Disadvantages – As with OO: stateful, etc
  21. 21. 3. FunctionalTurtle Data is immutable
  22. 22. Overview Client Turtle Functions state new state Uses immutable state state new state
  23. 23. type TurtleState = { position : Position angle : float<Degrees> penState : PenState } module Turtle = let move distance state = ... // return new state let turn angleToTurn state = ... // return new state let penUp state = ... // return new state let penDown log state = // return new state Public, immutable State passed in explicitly AND returned Data Behavior
  24. 24. Usage example let drawTriangle() = let s0 =Turtle.initialTurtleState let s1 =Turtle.move 50.0 s0 let s2 =Turtle.turn 120.0<Degrees> s1 let s3 =Turtle.move 50.0 s2 ... Passing state around is annoying and ugly!
  25. 25. Piping TurtleState Turtle function new TurtleState TurtleState Turtle function new TurtleState
  26. 26. Usage example with pipes let drawTriangle() = Turtle.initialTurtleState |>Turtle.move 50.0 |>Turtle.turn 120.0<Degrees> |>Turtle.move 50.0 ... |> is pipe operator
  27. 27. Advantages and disadvantages • Advantages – Immutability: Easy to reason about – Stateless: Easy to test – Functions are composable • Disadvantages – Client has to keep track of the state  – Hard-coded dependencies (for now)
  28. 28. 4. State monad Threading state behind the scenes
  29. 29. Turtle Canvas
  30. 30. Turtle Canvas Change implementation so that Move returns a pair: * New state, and * Actual distance moved
  31. 31. Usage example let s0 = Turtle.initialTurtleState let (actualDistA,s1) = Turtle.move 80.0 s0 if actualDistA < 80.0 then printfn "first move failed -- turning" let s2 =Turtle.turn 120.0<Degrees> s1 let (actualDistB,s3) = Turtle.move 80.0 s2 ... else printfn "first move succeeded" let (actualDistC,s2) = Turtle.move 80.0 s1 ... The returned pair
  32. 32. Transforming the function #1 TurtleState Turtle function new TurtleState Input Output
  33. 33. Transforming the function #2 Currying TurtleStateinput new TurtleState Output
  34. 34. Transforming the function #3 A function returns another function Input Turtle function
  35. 35. Transforming the function #4 Wrap this in a type called "State" For more on how this works, see fsharpforfunandprofit.com/monadster Input Turtle function State< >
  36. 36. Usage example let stateExpression = state { // "state" expression let! distA = move 80.0 if distA < 80.0 then printfn "first move failed -- turning" do! turn 120.0<Degrees> let! distB = move 80.0 ... else printfn "first move succeeded" let! distB = move 80.0 ... } State is threaded through behind the scenes Haskell has "do" notation. Scala has "for"comprehensions
  37. 37. Advantages and disadvantages • Advantages – Looks imperative, but preserves immutability. – Functions still composable • Disadvantages – Harder to implement and use
  38. 38. 5. Error handling
  39. 39. How to return errors? type Result<'successInfo,'errorInfo> = | Success of 'successInfo | Failure of 'errorInfo TurtleState Turtle function Success (new TurtleState) Input Failure (error message) Choice (aka Sum, aka Discriminated Union) type
  40. 40. let move distanceRequested state = // calculate new position // draw line if needed if actualDistanceMoved <> distanceRequested then Failure "Moved out of bounds" else Success {state with position = endPosition} Two different choices for return value (not easy in OO) Implementation using Result
  41. 41. Usage example let s0 = Turtle.initialTurtleState let result1 = s0 |> Turtle.move 80.0 match result1 with | Success s1 -> let result2 = s1 |> Turtle.move 80.0 match result2 with | Success s2 -> printfn "second move succeeded" ... | Failure msg -> printfn "second move failed: %s" msg ... | Failure msg -> printfn "first move failed -- %s" msg
  42. 42. Usage example let finalResult = result { // result expression let s0 = Turtle.initialTurtleState let! s1 = s0 |> Turtle.move 80.0 printfn "first move succeeded" let! s2 = s1 |> Turtle.move 30.0 printfn "second move succeeded" let! s3 = s2 |> Turtle.turn 120.0<Degrees> let! s4 = s3 |> Turtle.move 80.0 printfn "third move succeeded" return () } Errors are managed behind the scenes Combine "state" and "result" for even prettier code
  43. 43. Usage example let finalResult = resultState { // "result" and "state" combined do! Turtle.move 80.0 printfn "first move succeeded" do! Turtle.move 30.0 printfn "second move succeeded" do! Turtle.turn 120.0<Degrees> do! Turtle.move 80.0 printfn "third move succeeded" return () } Both errors and state are now managed behind the scenes
  44. 44. Advantages and disadvantages • Advantages – Errors are explicitly returned (no exceptions) – Looks like "happy path" code • Disadvantages – Slightly harder to implement and use For more, see fsharpforfunandprofit.com/rop
  45. 45. 5½. Async turtle What if the Turtle calls were async?
  46. 46. What if the Turtle calls were async? let s0 = Turtle.initialTurtleState s0 |> Turtle. moveAsync 80.0 (fun s1 -> s1 |> Turtle.moveAsync 80.0 (fun s2 -> s2 |> Turtle.moveAsync 80.0 (fun s3 -> ... ) ) ) Callbacks
  47. 47. Async usage example let finalResult = async { // "async" expression let s0 =Turtle.initialTurtleState let! s1 = s0 |> Turtle.moveAsync 80.0 let! s2 = s1 |> Turtle. moveAsync 80.0 let! s3 = s2 |> Turtle. moveAsync 80.0 ... } Callbacks are managed behind the scenes
  48. 48. Review: Common patterns • Composition – Chaining functions together • Explicitness – Explicit state management (no mutation) – Explicit errors (no exceptions) • Techniques to thread state/errors/callbacks behind the scenes (the m-word)
  49. 49. 6. Batch commands How can the caller avoid managing state?
  50. 50. Overview Turtle Functions Batch Runner Client state new staterun [command, command, command, command] Loop over commands
  51. 51. Overview Keep track of state here Turtle Functions Batch Runner [command, command, command, command] Client state new state Loop over commands run
  52. 52. How to convert a function into data? // Turtle functions let move distance = ... let turn angle = ... let penUp () = ... let penDown () = ... typeTurtleCommand = | Move of Distance |Turn of Angle | PenUp | PenDown Choice type
  53. 53. Usage example // create the list of commands let commands = [ Move 100.0 Turn 120.0<Degrees> Move 100.0 Turn 120.0<Degrees> Move 100.0 Turn 120.0<Degrees> ] // run them TurtleBatch.run commands This is *data* not function calls
  54. 54. "execute" implementation /// Apply a command to the turtle state and return the new state let executeCommand state command = match command with | Move distance -> Turtle.move distance state |Turn angleToTurn -> Turtle.turn angleToTurn state | PenUp -> Turtle.penUp state | PenDown -> Turtle.penDown state On-to-one correspondence
  55. 55. "run" implementation /// Run list of commands in one go let run aListOfCommands = let mutable state = Turtle.initialTurtleState for command in aListOfCommands do state <- executeCommand state command // return final state state
  56. 56. "run" implementation /// Run list of commands in one go let run aListOfCommands = aListOfCommands |> List.fold executeCommand Turtle.initialTurtleState Use built-in collection functions where possible
  57. 57. Advantages and disadvantages • Advantages – Decoupled – Simpler than monads! • Disadvantages – Batch oriented only – No control flow inside batch
  58. 58. 7.Actor model
  59. 59. Turtle Functions ActorcommandClient state new state queue message processing loop
  60. 60. Turtle Functions ActorcommandClient queue message processing loop Keep track of state here state new state
  61. 61. Pulling commands off a queue let rec loop turtleState = let command = // read a command from the message queue let newState = match command with | Move distance -> Turtle.move distance turtleState // etc loop newState Recurse with new state
  62. 62. Usage example // post a list of commands let turtleActor = newTurtleActor() turtleActor.Post (Move 100.0) turtleActor.Post (Turn 120.0<Degrees>) turtleActor.Post (Move 100.0) turtleActor.Post (Turn 120.0<Degrees>) Again, this is *data*
  63. 63. Advantages and disadvantages • Advantages – Decoupled – Simpler than state monad • Disadvantages – Extra boilerplate needed
  64. 64. 8. Event Sourcing How should we persist state?
  65. 65. Overview Turtle Functions Command Handler command Client state new state Recreate state Event Store Generate events write new events read previous events Execute command
  66. 66. Overview Turtle Functions Command Handler command Client state new state Recreate state Event Store Generate events write new events read previous events Execute command
  67. 67. Overview Turtle Functions Command Handler command Client state new state Recreate state Event Store Generate events write new events read previous events Execute command
  68. 68. Command vs. Event typeTurtleCommand = | Move of Distance |Turn of Angle | PenUp | PenDown What you *want* to have happen
  69. 69. Command vs. Event typeTurtleEvent = | Moved of Distance * StartPosition * EndPosition |Turned of AngleTurned * FinalAngle | PenStateChanged of PenState What *actually* happened (past tense) typeTurtleCommand = | Move of Distance |Turn of Angle | PenUp | PenDown Compare with the command
  70. 70. Implementation /// Apply an event to the current state and return the new state let applyEvent state event = match event with | Moved (distance,startPosition,endPosition) -> {state with position = endPosition } // don't callTurtle |Turned (angleTurned,finalAngle) -> {state with angle = finalAngle} | PenStateChanged penState -> {state with penState = penState} Important! No side effects when recreating state!
  71. 71. Implementation let handleCommand (command:TurtleCommand) = // First load all the events from the event store let eventHistory = EventStore.getEvents() //Then, recreate the state before the command let stateBeforeCommand = eventHistory |> List.fold applyEvent Turtle.initialTurtleState // Create new events from the command let newEvents = executeCommand command stateBeforeCommand // Store the new events in the event store events |> List.iter (EventStore.saveEvent) This is where side-effects happen No side effects
  72. 72. Advantages and disadvantages • Advantages – Decoupled – Stateless – Supports replay of events • Disadvantages – More complex – Versioning
  73. 73. 9. Stream Processing
  74. 74. Overview Stream processor Stream processor Command Handler commands event stream new events Select, Filter Transform event stream event stream event stream other event streams Stream processor new events other event streams Event Store
  75. 75. Overview Stream processor Stream processor Command Handler commands event stream new events Select, Filter Transform event stream event stream event stream other event streams Stream processor new events other event streams Event Store
  76. 76. Overview Stream processor Stream processor Command Handler commands event stream new events Select, Filter Transform event stream event stream event stream other event streams Stream processor new events other event streams Event Store Make decisions based on the upstream events
  77. 77. Turtle stream processing example TurtleState processor Command Handler command Event Store
  78. 78. Turtle stream processing example TurtleState processor Audit processor Command Handler event streamcommand Canvas processor Event Store Distance processor
  79. 79. Stream processing demo Auditing Processor Canvas Processor DistanceTravelled Processor
  80. 80. Advantages and disadvantages • Advantages – Same as Event Sourcing, plus – Separates state logic from business logic – Microservice friendly! • Disadvantages – Even more complex!
  81. 81. Review • "Conscious decoupling" with data types – Passing data instead of calling functions • Immutable data stores – Storing event history rather than current state
  82. 82. 10. OO style dependency injection
  83. 83. Overview Turtle Class Move ICanvas ILogger Inject Turn PenUp
  84. 84. Same for the Turtle client Client DrawTriangle ITurtle Inject DrawSquare
  85. 85. OO dependency injection demo
  86. 86. Advantages and disadvantages • Advantages – Well understood • Disadvantages – Unintentional dependencies – Interfaces often not fine grained – Often requires IoC container or similar
  87. 87. 11. FP style dependency injection
  88. 88. Overview Turtle Module Move drawlogInfo Turn logInfo PenUp logInfo
  89. 89. Overview MovelogInfo draw Distance Output
  90. 90. Overview logInfo draw Distance Output Partial Application
  91. 91. Overview Move logInfo draw Distance Output Dependencies are "baked in"
  92. 92. "move" implementation let move logInfo draw distance state = logInfo (sprintf "Move %0.1f" distance) // calculate new position ... // draw line if needed if state.penState = Down then draw startPosition endPosition // update the state ... Function parameters
  93. 93. "turn" implementation let turn logInfo angleToTurn state = logInfo (sprintf "Turn %0.1f" angleToTurn) // calculate new angle let newAngle = ... // update the state ... Function parameter
  94. 94. Turtle.initialTurtleState |> move 50.0 |> turn 120.0<Degrees> Partial application in practice let move =Turtle.move Logger.info Canvas.draw // output is new function: "move" // (Distance -> TurtleState ->TurtleState) let turn = Turtle.turn Logger.info // output is new function: "turn" // (float<Degrees> ->TurtleState ->TurtleState) partial application Use new functions here
  95. 95. FP dependency injection demo
  96. 96. Advantages and disadvantages • Advantages – Dependencies are explicit – Functions, not interfaces – Counterforce to having too many dependencies (ISP for free!) – Built in! No special libraries needed • Disadvantages – ??
  97. 97. 12. Interpreter
  98. 98. APIs create coupling module TurtleAPI = move : Distance -> State -> Distance * State turn : Angle -> State -> State penUp : State -> State penDown : State -> State Fine, but what if the API changes to return Result?
  99. 99. APIs create coupling module TurtleAPI = move : Distance -> State -> Result<Distance * State> turn : Angle -> State -> Result<State> penUp : State -> Result<State> penDown : State -> Result<State> Fine, but what if it needs to be Async as well?
  100. 100. APIs create coupling module TurtleAPI = move : Distance -> State -> AsyncResult<Distance * State> turn : Angle -> State -> AsyncResult<State> penUp : State -> AsyncResult<State> penDown : State -> AsyncResult<State> Each change breaks the caller  Solution: decouple using data instead of functions! But how to manage control flow?
  101. 101. Overview Move response Interpreter"Program" data (e.g. Move)Client Turn response Program data (e.g. Turn) Program etc ...
  102. 102. type TurtleProgram = // (input params) (response) | Move of Distance * (Distance -> TurtleProgram) | Turn of Angle * (unit -> TurtleProgram) | PenUp of (* none *) (unit -> TurtleProgram) | PenDown of (* none *) (unit -> TurtleProgram) | Stop Create a "Program" type Input Output from interpreter Next step for the interpreter New case needed!
  103. 103. Usage example let drawTriangle = Move (100.0, fun actualDistA -> // actualDistA is response from interpreter Turn (120.0<Degrees>, fun () -> // () is response from interpreter Move (100.0, fun actualDistB -> Turn (120.0<Degrees>, fun () -> Move (100.0, fun actualDistC -> Turn (120.0<Degrees>, fun () -> Stop)))))) Ugly!
  104. 104. Usage example let drawTriangle = turtleProgram { // Seen this trick before! let! actualDistA = move 100.0 do! turn 120.0<Degrees> let! actualDistB = move 100.0 do! turn 120.0<Degrees> let! actualDistC = move 100.0 do! turn 120.0<Degrees> }
  105. 105. Overview TurtleProgram A set of instructions Interpreter A Interpreter B Interpreter C
  106. 106. Example:Turtle Interpreter let rec interpretAsTurtle state program = match program with | Stop -> state | Move (dist, next) -> let actualDistance, newState = Turtle.move dist state let nextProgram = next actualDistance // next step interpretAsTurtle newState nextProgram |Turn (angle, next) -> let newState = Turtle.turn angle state let nextProgram = next() // next step interpretAsTurtle newState nextProgram Move the turtle Execute the next step
  107. 107. Example: Distance Interpreter let rec interpretAsDistance distanceSoFar program = match program with | Stop -> distanceSoFar | Move (dist, next) -> let newDistance = distanceSoFar + dist let nextProgram = next newDistance interpretAsDistance newDistance nextProgram |Turn (angle, next) -> // no change in distanceSoFar let nextProgram = next() interpretAsDistance distanceSoFar nextProgram Calculate the new distance Execute the next step
  108. 108. Interpreter demo
  109. 109. Advantages and disadvantages • Advantages – Completely decoupled – Pure API – Optimization possible • Disadvantages – Complex – Best with limited set of operations Examples: Twitter's "Stitch" library, Facebook's "Haxl"
  110. 110. 13. Capabilities
  111. 111. Overview APIcallClient call I'm sorry, Dave, I'm afraid I can't do that
  112. 112. Rather than telling me what I can't do, why not tell me what I can do?
  113. 113. Capability-based API
  114. 114. Overview APIcall Some capabilities Client
  115. 115. Overview APIcallClient Use a capability
  116. 116. Turtle API capabilities Turtle APIMoveClient Move, Turn, PenDown Move Move, Turn, PenDown OK Hit the edge
  117. 117. Turtle Capabilities type TurtleCapabilities = { move : MoveFn option turn : TurnFn penUp : PenUpDownFn penDown: PenUpDownFn } and MoveFn = Distance -> TurtleCapabilities and TurnFn = Angle -> TurtleCapabilities and PenUpDownFn = unit -> TurtleCapabilities
  118. 118. Usage example let turtleCaps = Turtle.start() // initial set of capabilities match turtleCaps.move with | None -> warn "Error: Can't do move 1" turtleCaps.turn 120<Degrees> | Some moveFn -> // OK let turtleCaps = moveFn 60.0 // a new set of capabilities match turtleCaps.move with | None -> warn "Error: Can't do move 2" turtleCaps.turn 120<Degrees> | Some moveFn -> ...
  119. 119. Capabilities demo
  120. 120. Advantages and disadvantages • Advantages – Client doesn't need to duplicate business logic – Better security – Capabilities can be transformed for business rules • Disadvantages – Complex to implement – Client has to handle unavailable functionality Examples: HATEOAS More at fsharpforfunandprofit.com/cap
  121. 121. Slides and video here F# consulting Phew! fsharpforfunandprofit.com/turtle

×