GenStatem

Event-Driven FSM

See: Erlang gen_statem Behaviour

  1. State(S) x Event(E) -> Actions(A), State(S')

GenStatem Typeclass

  1. class GenStatem e s d | e -> s, s -> d, d -> e where
  2. handleEvent :: HandleEvent e s d

CodeLock Example

  1. module Demo.FSM.CodeLock
  2. ( name
  3. , start
  4. , push
  5. , stop
  6. ) where
  7. import Prelude
  8. import Control.Behaviour.GenStatem
  9. ( class GenStatem
  10. , Action(..)
  11. , EventType(..)
  12. , Init
  13. , OnEvent
  14. , initOk
  15. , handleWith
  16. , unhandled
  17. )
  18. import Control.Behaviour.GenStatem as FSM
  19. data Event = Button Integer | Lock
  20. data State = Locked | Opened
  21. data Data = Data
  22. { code :: [Integer]
  23. , length :: Integer
  24. , buttons :: [Integer]
  25. }
  26. instance Eq State where
  27. eq Locked Locked = true
  28. eq Opened Opened = true
  29. eq _ _ = false
  30. instance GenStatem Event State Data where
  31. handleEvent = handleWith [(Locked, locked), (Opened, opened)]
  32. name :: Atom
  33. name = :code_lock
  34. start :: [Integer] -> Process Pid
  35. start code = FSM.startLinkWith name (init code)
  36. push :: Integer -> Process ()
  37. push n = FSM.cast name (Button n)
  38. stop :: Process ()
  39. stop = FSM.stop name
  40. init :: [Integer] -> Init Event State Data
  41. init code = initOk Locked d
  42. where d = Data $ { code = reverse code
  43. , length = length code
  44. , buttons = []
  45. }
  46. locked :: OnEvent Event State Data
  47. locked Cast (Button n) (Data d) =
  48. let buttons = take d.length [n|d.buttons]
  49. in if buttons == d.code then
  50. let actions = [StateTimeout 1000 Lock] in
  51. FSM.nextWith Opened (Data d{buttons = []}) actions
  52. else FSM.keep (Data d{buttons = buttons})
  53. locked t e d = unhandled t e Locked d
  54. opened :: OnEvent Event State Data
  55. opened Cast (Button _) d = FSM.keep d
  56. opened Timeout Lock d = do
  57. println "Timeout Lock"
  58. FSM.next Locked d
  59. opened t e d = unhandled t e Opened d

Start a FSM process

  1. -- | Start a standalone FSM process
  2. start :: forall e s d. GenStatem e s d => (Init e s d) -> Process Pid
  3. startWith :: forall e s d. GenStatem e s d => Name -> (Init e s d) -> Process Pid
  4. -- | Start a FSM process as part of a supervision tree.
  5. startLink :: forall e s d. GenStatem e s d => (Init e s d) -> Process Pid
  6. startLinkWith :: forall e s d. GenStatem e s d => Name -> (Init e s d) -> Process Pid

Init callback

  1. -- | Init Result
  2. data InitResult e s d
  3. = InitOk s d [Action e]
  4. -- ^ {ok, State, Actions}
  5. | InitIgnore
  6. -- ^ ignore
  7. | InitStop ExitReason
  8. -- ^ {stop, Reason}
  9. -- | Init Action
  10. type Init e s d = Process (InitResult e s d)

HandleEvent callback

  1. -- | Event Type
  2. data EventType
  3. = Call From | Cast | Info
  4. -- ^ external event type
  5. | Timeout
  6. -- ^ timeout event type
  7. | Internal
  8. -- ^ internal
  9. -- | Statem Transition
  10. data Transition e s d
  11. = Keep d [Action e]
  12. | Next s d [Action e]
  13. | Repeat d [Action e]
  14. | Shutdown ExitReason d
  15. type HandleEvent e s d = EventType -> e -> s -> d -> Process (Transition e s d)
  16. -- | On Event
  17. type OnEvent e s d = EventType -> e -> d -> Process (Transition e s d)
  18. -- | Handle with state functions.
  19. handleWith :: forall e s d. [(s, OnEvent e s d)] -> HandleEvent e s d

Client APIs

  1. call :: forall req rep. Name -> req -> Process rep
  2. cast :: forall msg. Name -> msg -> Process ()