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

Ghost in the State Machine

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

Markus Wein

February 12, 2015
Tweet

More Decks by Markus Wein

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. new payment waiting submit waiting for processing payment received payment

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

    for shipping process cancel payment shipped ship cancel waiting for return cancel retu
  7. 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) }
  8. 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
  9. 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 } } } }
  10. 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] ]
  11. 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) {
  12. } } 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() } } }
  13. 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