Implementation using Transit

Now let’s see how Transit can help us to improve this.

State and Message Types

Transit uses Variant1 types for both State and Msg instead of traditional ADTs. This design choice is crucial for Transit, but for now let’s just focus on the fact that it’s another way to represent sum types.2

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

type Msg = Variant
  ( "Close" :: {}
  , "Open" :: {}
  )

🗎 test/Examples/Door.purs L27-L35

The empty record {} is used to represent the absence of any data (payload) associated with the state or message.

Transit Specification

Once the types are defined, we can define the state machine structure using Transit’s type-level DSL. Let’s see what it looks like:

type DoorTransit =
  Transit
    :* ("DoorOpen" :@ "Close" >| "DoorClosed")
    :* ("DoorClosed" :@ "Open" >| "DoorOpen")

🗎 test/Examples/Door.purs L37-L40

Breaking down the syntax:

For instance, we read the first transition as: in state DoorOpen, when receiving message Close, transition to state DoorClosed.

This type-level specification fully defines the state machine’s structure. The compiler can now use it to ensure our implementation of the update function matches the specification.

The Update Function

Based on the above specification, we create an update function using mkUpdate:

update :: State -> Msg -> State
update = mkUpdate @DoorTransit
  (match @"DoorOpen" @"Close" \_ _ -> return @"DoorClosed")
  (match @"DoorClosed" @"Open" \_ _ -> return @"DoorOpen")

🗎 test/Examples/Door.purs L42-L45

Here’s how this works:


  1. The purescript-variant library provides row-polymorphic sum types. See the documentation for more details.↩︎

  2. This design choice is crucial for Transit’s type-level machinery. The key advantage is that Transit can filter the possible cases (both input states/messages and output states) for each handler function. Variants are perfect for this. There is no way to express a subset of cases from a traditional ADT.↩︎

  3. In PureScript, the @ symbol is used for explicit type application, allowing you to pass type-level arguments to functions.↩︎

↑ Back to top