Skip to content

Instantly share code, notes, and snippets.

@galanggg
Forked from rtfeldman/GameState.elm
Created September 11, 2023 07:22
Show Gist options
  • Save galanggg/f45fe61485bbe33c648176ece6667d3d to your computer and use it in GitHub Desktop.
Save galanggg/f45fe61485bbe33c648176ece6667d3d to your computer and use it in GitHub Desktop.
Exploring State Machines with phantom types in Elm
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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment