Skip to content

Instantly share code, notes, and snippets.

@rupertlssmith
Last active January 11, 2018 16:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rupertlssmith/c948f304ce0a99cd7ba9d6b6a061251a to your computer and use it in GitHub Desktop.
Save rupertlssmith/c948f304ce0a99cd7ba9d6b6a061251a to your computer and use it in GitHub Desktop.
Experimenting with state machines in Elm
type alias WithContent =
{ contentItem : Content }
type alias WithSelectedModel =
{ contentModel : ContentModel
, overlay : Overlay.Model
}
type alias WithInlineEditor =
{ inlineEditorStyle : Animation.State
}
type Mode
= Loading
| Explore WithContent
| Markdown WithContent WithSelectedModel
| Preview WithContent WithSelectedModel
| Wysiwyg WithContent WithSelectedModel WithInlineEditor
type alias Model =
{ mode : Mode
, menu : Menu
}
maybeLoading : Mode -> Maybe Mode
maybeLoading state =
case state of
Loading ->
Just state
_ ->
Nothing
-- ... more maybeState functions
mapWhenWithContent : (WithContent -> a) -> Mode -> Maybe a
mapWhenWithContent func state =
case state of
Explore content ->
Just <| func content
Markdown content _ ->
Just <| func content
Preview content _ ->
Just <| func content
Wysiwyg content _ _ ->
Just <| func content
_ ->
Nothing
-- ... More mapWhen functions
updateWhenWithContent : (WithContent -> WithContent) -> Mode -> Maybe Mode
updateWhenWithContent func state =
case state of
Explore content ->
func content |> Explore |> Just
Markdown content selected ->
func content |> (flip Markdown) selected |> Just
Preview content selected ->
func content |> (flip Preview) selected |> Just
Wysiwyg content selected inline ->
func content |> (swirll Wysiwyg) selected inline |> Just
_ ->
Nothing
-- ... More updateWhen functions
-- State transition functions - these may contain custom logic, unlike the above which area really just boilerplate.
toLoading : Mode -> Mode
toLoading _ =
Loading
toExplore : WithContent -> Mode -> Maybe Mode
toExplore content state =
case state of
Loading ->
Nothing
_ ->
Explore content |> Just
toMarkdown : WithSelectedModel -> Mode -> Maybe Mode
toMarkdown selected state =
case state of
Loading ->
Nothing
_ ->
mapWhenWithContent
(\content ->
Markdown content selected
)
state
toPreview : WithSelectedModel -> Mode -> Maybe Mode
toPreview selected state =
case state of
Loading ->
Nothing
_ ->
mapWhenWithContent
(\content ->
Preview content selected
)
state
toWysiwyg : WithSelectedModel -> WithInlineEditor -> Mode -> Maybe Mode
toWysiwyg selected inline state =
case state of
Loading ->
Nothing
_ ->
mapWhenWithContent
(\content ->
Wysiwyg content selected inline
)
state
module StateModel
exposing
( boolToMaybe
, (>&&>)
, (>||>)
, defaultTransition
, mapWhenCompose
)
import Maybe exposing (andThen)
import Maybe.Extra exposing (orElse)
boolToMaybe : (a -> Bool) -> a -> Maybe a
boolToMaybe filter val =
if filter val then
Just val
else
Nothing
(>&&>) : (a -> Maybe b) -> (b -> Maybe c) -> a -> Maybe c
(>&&>) fst snd val =
fst val |> andThen snd
(>||>) : (a -> Maybe b) -> (a -> Maybe b) -> a -> Maybe b
(>||>) fst snd val =
fst val |> orElse (snd val)
defaultTransition state trans =
trans state |> Maybe.withDefault state
mapWhenCompose : (c -> d -> Maybe a) -> (a -> d -> Maybe b) -> c -> d -> Maybe b
mapWhenCompose mapWhen1 mapWhen2 func state =
mapWhen1 func state
|> andThen ((flip mapWhen2) state)
@rupertlssmith
Copy link
Author

This shows a data modelling pattern in Elm. The data model (Model) is a product (Mode) of a sum of little products (WithContent, WithSelectedModel, WithInlineEditor). Functions of this model tend to be written as functions of the little products and this allows a good deal of flexibility since the same functions may work in many states.

Larger scale reorganization of the code as the state machine described by the sum (Mode) is changed can often be performed with less changes to the code than might otherwise be needed.

@rupertlssmith
Copy link
Author

StateModel.elm contains some helper functions.

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