This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into some other state in a state machine.
This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data, and functions need to be mapped over that data or updates to it made, rather than just a pure state machine.
As the example runs it prints out the states, showing how the shape of the model varies as the state machine runs. This is the point of using the state machine; it only makes available fields in the model that need to exist in any given state. This removes the need for lots of fields in the model to by 'Maybe's, or to have lots of 'Bool' flags in the model to indicate when certain states are valid:
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 state machine exported by this module can only be manipulated in legal ways by code consuming the module. A constructor function is provided to create the initial state 'loading' but from there the 'toX' functions must be used to move through states without bypassing the rules of the state machine.
Note: The 'State' type is fully exposed in the the-sett/elm-state-machines package here: [ https://github.com/the-sett/elm-state-machines/blob/1.0.0/src/StateMachine.elm#L27 ]. This means that it is possible to create illegal states and use the exposed Game constructors to build them. The alternative would be to not expose the 'State' type, but that would require that each state machine define its own private version of it with 'map' and 'untag' functions. In order to cheat in this way, the consumer of the state machine would need to import the StateMachine module, and use it to build illegal states.