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 whilst remaining in the current state, rather than just a pure state machine.
-
-
Save galanggg/f45fe61485bbe33c648176ece6667d3d to your computer and use it in GitHub Desktop.
Exploring State Machines with phantom types in Elm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Game exposing (..) | |
| type Allowed | |
| = Allowed | |
| type alias GameDefinition = | |
| { boardSize : Int | |
| } | |
| type alias PlayState = | |
| { score : Int | |
| , position : List Int | |
| } | |
| type State | |
| = Loading | |
| | Ready | |
| | InPlay | |
| | GameOver | |
| type Game pre model | |
| = Game model | |
| type alias Loading = | |
| Game {} { state : State } | |
| type alias Ready = | |
| Game { loading : Allowed, gameOver : Allowed } { state : State, definition : GameDefinition } | |
| type alias InPlay = | |
| Game { ready : Allowed } { state : State, definition : GameDefinition, play : PlayState } | |
| type alias GameOver = | |
| Game { inPlay : Allowed } { state : State, definition : GameDefinition, finalScore : Int } | |
| -- State constructors. | |
| loading : Loading | |
| loading = | |
| Game { state = Loading } | |
| ready : GameDefinition -> Ready | |
| ready definition = | |
| Game | |
| { state = Ready | |
| , definition = definition | |
| } | |
| inPlay : GameDefinition -> PlayState -> InPlay | |
| inPlay definition play = | |
| Game | |
| { state = InPlay | |
| , definition = definition | |
| , play = play | |
| } | |
| gameOver : GameDefinition -> Int -> GameOver | |
| gameOver definition score = | |
| Game | |
| { state = GameOver | |
| , definition = definition | |
| , finalScore = score | |
| } | |
| -- Map functions that can be applied when parts of the model are present. | |
| mapGameDefinition : (GameDefinition -> a) -> Game p { m | definition : GameDefinition } -> a | |
| mapGameDefinition func (Game model) = | |
| func model.definition | |
| -- ... more mapping functions | |
| -- Update functions that can be applied when parts of the model are present. | |
| updateGameDefinition : | |
| (GameDefinition -> GameDefinition) | |
| -> Game p { m | definition : GameDefinition } | |
| -> Game p { m | definition : GameDefinition } | |
| updateGameDefinition func (Game model) = | |
| Game { model | definition = func model.definition } | |
| -- ... more update functions | |
| -- State transition functions that can be applied only to states that are permitted | |
| -- to make a transition. | |
| toReady : Game { a | ready : Allowed } { m | definition : GameDefinition } -> Ready | |
| toReady (Game model) = | |
| ready model.definition | |
| toReadyWithGameDefinition : GameDefinition -> Game { a | ready : Allowed } m -> Ready | |
| toReadyWithGameDefinition definition game = | |
| ready definition | |
| toInPlay : PlayState -> Game { a | inPlay : Allowed } { m | definition : GameDefinition } -> InPlay | |
| toInPlay play (Game model) = | |
| inPlay model.definition play | |
| toGameOver : Game { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> GameOver | |
| toGameOver (Game model) = | |
| gameOver model.definition model.play.score |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment