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.