Can phantom types be used to restrict the possible transitions in a state machine?

So I updated the Gist with some fresh ideas.

An important aspect of working with phantom types would seem to be that the phantom type cannot be exposed at the top-level in your model, otherwise you run into the chicken and egg situation of needing to know exactly what type you are currently modelling at the same time as using it as a type that has been deliberately designed to be varied in different situations; you can’t have it both ways.

So I had to make sure the phantom type was buried within the state machine:

type Allowed
    = Allowed

type State trans model
    = State model

type Game
    = Loading Loading
    | Ready Ready
    | InPlay InPlay
    | GameOver GameOver

type alias Loading =
    State { ready : Allowed } {}


type alias Ready =
    State { inPlay : Allowed } { definition : GameDefinition }


type alias InPlay =
    State { gameOver : Allowed } { definition : GameDefinition, play : PlayState }


type alias GameOver =
    State { ready : Allowed } { definition : GameDefinition, finalScore : Int }

For each state in the state machine, I can extract as a ‘State’ something that tells me what state transitions are possible, and what the shape of the model in that state looks like.

The example in the Gist runs through the state machine for the game, and prints out the states. As you read down the output you can see how the shape of the model varies as the game progresses, and that only fields that are relevant to each state exist when that state is active (make invalid states unreachable):

Loading (State {})

Ready (State { definition = { boardSize = 100 } })

InPlay (State { definition = { boardSize = 100 }, play = { score = 0, position = [] } })

GameOver (State { definition = { boardSize = 100 }, finalScore = 123 })

Ready (State { definition = { boardSize = 100 } })

The current state is pattern matched along with the Msg in the update function (as per Brian’s suggestion). From there on the type variables on the State type keep the map, update and state transition functions total, eliminating the need to deal with Maybes that I am already sure about.

It adds a degree of complexity, but also some parts of the code are more concise than what I was doing before. On the whole I think its an improvement.

2 Likes