Implementation using Transit

State and Message Types

Also in the Transit approach we define State and Msg types. This time some cases of those types have data attached to them:

type State = Variant
  ( "DoorOpen" :: {}
  , "DoorClosed" :: {}
  , "DoorLocked" :: { storedPin :: String }
  )

type Msg = Variant
  ( "Close" :: {}
  , "Open" :: {}
  , "Lock" :: { newPin :: String }
  , "Unlock" :: { enteredPin :: String }
  )

🗎 test/Examples/DoorPin.purs L31-L42

Transit Specification

In the DSL specification, we express conditional transitions by listing multiple possible target states:

type DoorPinTransit =
  Transit
    :* ("DoorOpen" :@ "Close" >| "DoorClosed")
    :* ("DoorClosed" :@ "Open" >| "DoorOpen")
    :* ("DoorClosed" :@ "Lock" >| "DoorLocked")
    :*
      ( "DoorLocked" :@ "Unlock"
          >| ("PinCorrect" :? "DoorClosed")
          >| ("PinIncorrect" :? "DoorLocked")
      )

🗎 test/Examples/DoorPin.purs L44-L53

The syntax ("PinCorrect" :? "DoorClosed") >| ("PinIncorrect" :? "DoorLocked") indicates that the Unlock message from DoorLocked can transition to either state, depending on runtime conditions. The :? operator associates a condition label (like "PinCorrect") with a target state, and >| chains multiple conditional outcomes together.

The Update Function

The handlers in the update function now have access to both the matching state and message data, allowing you to implement the conditional runtime logic for the transition.

update :: State -> Msg -> State
update = mkUpdate @DoorPinTransit
  ( match @"DoorOpen" @"Close" \_ _ ->
      return @"DoorClosed"
  )
  ( match @"DoorClosed" @"Open" \_ _ ->
      return @"DoorOpen"
  )
  ( match @"DoorClosed" @"Lock" \_ msg ->
      return @"DoorLocked" { storedPin: msg.newPin }
  )
  ( match @"DoorLocked" @"Unlock" \state msg ->
      let
        isCorrect = state.storedPin == msg.enteredPin
      in
        if isCorrect then
          returnVia @"PinCorrect" @"DoorClosed"
        else
          returnVia @"PinIncorrect" @"DoorLocked" { storedPin: state.storedPin }
  )

🗎 test/Examples/DoorPin.purs L55-L74

The order of match handlers in mkUpdate must match the order of transitions in the DSL specification. The compiler can detect if the returned state of a handler is legal for a given transition. However, it cannot detect if an implementation forgets to return a possible case. For example, if a transition can return either DoorClosed or DoorLocked, but your handler always returns DoorClosed, the compiler would not detect this error. The compiler cannot verify whether your handler implements the conditional logic correctly, so missing a case is just one of many possible errors.

↑ Back to top