Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ghost in the State Machine

Bbe8195463ff7259a5f33e01e139eade?s=47 Markus Wein
February 12, 2015

Ghost in the State Machine

A short talk about Finite State Machines I gave at the Vienna.rb 2015-02-12 Meetup

Bbe8195463ff7259a5f33e01e139eade?s=128

Markus Wein

February 12, 2015
Tweet

Transcript

  1. Ghost in the finite State Machine

  2. None
  3. What happens after you click “Buy Now”?

  4. We now have a NEW order

  5. WAITING FOR PAYMENT

  6. What if Payment doesn’t arrive?

  7. Payment has arrived

  8. None
  9. “Hey, your Package has shipped”

  10. Cancel your order?

  11. “What we've got here is failure to communicate implement a

    FSM”
  12. Signs you have a State machine, but maybe don’t know

    it yet
  13. 1) Lots of booleanS (timestamps count too)

  14. 2) You have a field called `state` or `status`

  15. How to implement a state machine?

  16. We need states

  17. We need transitions between states

  18. We can implement it ourselves

  19. We have the technology

  20. the 6_000_000 states machine man

  21. state_machine

  22. require 'state_machine' class Order state_machine :state, :initial => :new do

    around_transition do |order, transition, block| puts "Starting transition from #{transition.from}" block.call puts "Finished transition to #{transition.to}" end event :submit do transition :new => :payment_waiting end event :payment_received do transition :payment_waiting => :waiting_for_processing end event :payment_failed do transition :payment_waiting => :payment_failed
  23. event :payment_received do transition :payment_waiting => :waiting_for_processing end event :payment_failed

    do transition :payment_waiting => :payment_failed end event :retry_payment do transition :payment_failed => :payment_waiting end event :process do transition :waiting_for_processing => :waiting_for_shipping end event :ship do transition :waiting_for_shipping => :shipped end after_transition :on => :ship, :do => :notify_customer
  24. end after_transition :on => :ship, :do => :notify_customer # Multiple

    transitions, first matching is taken event :cancel do transition :waiting_for_processing => :canceled transition :waiting_for_shipping => :canceled transition :shipped => :waiting_for_return end event :return_shipment_received do transition :waiting_for_return => :canceled end end def notify_customer puts "Your package has been shipped! :shipit:" end end
  25. end end if __FILE__ == $0 order_fsm = Order.new order_fsm.submit!

    order_fsm.payment_received! order_fsm.process! order_fsm.ship! puts "\n\n" print "Received payment twice (soft): “ order_fsm.payment_received puts order_fsm.state puts "Received payment twice: “ order_fsm.payment_received! end
  26. None
  27. rake state_machine:draw \ CLASS=Order \ FILE=./order_fsm.rb \ FORMAT=pdf \ ORIENTATION=landscape

    \ HUMAN_NAMES=true
  28. new payment waiting submit waiting for processing payment received payment

    failed payment failed waiting fo process retry payment
  29. waiting for processing nt received payment failed ent failed waiting

    for shipping process cancel payment shipped ship cancel waiting for return cancel retu
  30. for shipping canceled cancel shipped ship cancel waiting for return

    cancel return shipment received
  31. Acts as state machine (aasm)

  32. Everything has state

  33. Use an FSM before it is too late

  34. “Why developers should be force-fed state machines”

  35. fin

  36. BONUS!

  37. A state machine in swift via http://www.figure.ink/blog/2015/2/9/swift-state-machines-part-4-redirect

  38. import Foundation class StateMachine<P:StateMachineDelegateProtocol> { private unowned let delegate:P private

    let validTransitions: [P.StateType: [P.StateType]] private var _state:P.StateType{ didSet { delegate.didTransitionFrom(oldValue, to:_state) } } var state:P.StateType { get{ return _state } set{ // Can't be an observer because we need the option to CONDITIONALLY set state attemptTransitionTo(newValue) }
  39. delegate.didTransitionFrom(oldValue, to:_state) } } var state:P.StateType { get{ return _state

    } set{ // Can't be an observer because we need the option to CONDITIONALLY set state attemptTransitionTo(newValue) } } init(initialState:P.StateType, delegate:P, validTransitions: [P.StateTy [P.StateType]]) { _state = initialState //set the primitive to avoid calling the delegate. self.validTransitions = validTransitions
  40. init(initialState:P.StateType, delegate:P, validTransitions: [P.StateTy [P.StateType]]) { _state = initialState //set

    the primitive to avoid calling the delegate. self.validTransitions = validTransitions self.delegate = delegate } private func attemptTransitionTo(to:P.StateType) { if let validNexts = validTransitions[_state] { if contains(validNexts, to) { _state = to } else { // error, etc } } } }
  41. import UIKit class Example : UIView { private var machine:StateMachine<Example>!

    enum TrafficLight : Int { case Stop, Go, Caution } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let tx = [ TrafficLight.Stop: [TrafficLight.Go], TrafficLight.Caution: [TrafficLight.Stop], TrafficLight.Go: [TrafficLight.Caution] ]
  42. required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let tx =

    [ TrafficLight.Stop: [TrafficLight.Go], TrafficLight.Caution: [TrafficLight.Stop], TrafficLight.Go: [TrafficLight.Caution] ] machine = StateMachine(initialState: .Stop, delegate: self, validTransitions: tx) } @IBAction func tappedGo(sender:AnyObject) { machine.state = .Go } @IBAction func tappedCaution(sender:AnyObject) {
  43. } } extension Example : StateMachineDelegateProtocol { typealias StateType =

    TrafficLight func didTransitionFrom(from: StateType, to: StateType) { switch to{ case .Stop: backgroundColor = UIColor.redColor() case .Go: backgroundColor = UIColor.greenColor() case .Caution: backgroundColor = UIColor.yellowColor() } } }
  44. via https://daniel-levin.github.io/2015/01/19/primitive-state-machine-in-haskell.html

  45. data State = S0 | S1 | S2 accepts ::

    State -> String -> Bool accepts S0 ('a':xs) = accepts S1 xs accepts S0 ('b':xs) = accepts S2 xs accepts S1 ('a':xs) = accepts S2 xs accepts S1 ('b':xs) = accepts S0 xs accepts S2 ('a':xs) = accepts S0 xs accepts S2 ('b':xs) = accepts S2 xs accepts S2 _ = True accepts _ _ = False decide :: String -> Bool decide = accepts S0
  46. None