oofp / Beseder

Beseder: make impossible state transitions impossible
BSD 3-Clause "New" or "Revised" License
32 stars 0 forks source link

Beseder

Library for typesafe state machines. Make impossible state transitions impossible and more

The Hebrew word for “okay” and for “alright” is “beseder” (be-se-der)

Beseder goals:

Beseder inspirations:

Beseder core concepts:

--create resource
class MkRes ctx res  where
    type ResSt ctx res  :: *
    mkRes :: res -> ctx (ResSt ctx res)

--clear resource    
class TermState ctx state where
    terminate :: state -> ctx ()

--unsolicted resource state transition    
class Transition ctx state1 where
    type NextStates state1 :: [*]
    next :: state1 -> (V (NextStates state1) -> ctx Bool) -> ctx Bool

-- request invocation    
class Request ctx req state1 where
    type ReqResult (req :: *) (st :: *) :: [*]
    request :: req -> state1 -> ctx (V (ReqResult req state1))

Resource definition

The simplest way to create new resource is to use type class that defines resource initial and terminated states, as well as supported requests and state transitions for every state. Then we call buildRes that uses Template Haskell to generate all required declarations. As a bonus buildRes also generates resource state diagram

CardReader

Example of Beseder application (inspired by my condo door behavior):

Door (door resource) gets open when either the fob is read (fobReader) or internal proximity sensor (inDet) was triggered. The door should stay open for predefined time interval (doorTimeoutSec). The door should stay open as long as at least one of proximity sensors (inDet or outDet) are on

doorHandler doorTimeoutSec = 
  handleEvents $ do
    on @("fobReader" :? IsMessageReceived) $ do 
      invoke #fobReader GetNextMsg
      openDoorIfClosed doorTimeoutSec       
    on @("inDet" :? IsBinMonitorOn) $ do 
      openDoorIfClosed doorTimeoutSec    
    on @("doorTimer" :? IsTimerTriggered) $ do 
      onOrElse @("inDet" :? IsBinMonitorOn :|| "outDet" :? IsBinMonitorOn)
        (restartTimer doorTimeoutSec)
        closeDoor        

openDoorIfClosed :: Int -> STransData m sp _ ()     
openDoorIfClosed doorTimeoutSec = do
  on @("door" :? IsBinSwitchOff) $ do
    invoke #door TurnOn
    newRes #doorTimer TimerRes
    invoke #doorTimer (StartTimer doorTimeoutSec)

closeDoor :: STransData m sp _ () 
closeDoor = do
  clear #doorTimer   
  invoke #door TurnOff

restartTimer :: Int -> STransData m sp _ () 
restartTimer doorTimeoutSec = do
  clear #doorTimer
  newRes #doorTimer TimerRes
  invoke #doorTimer (StartTimer doorTimeoutSec)

and here is state diagram extracted for this application:

EntranceDoor