Skip to content

Instantly share code, notes, and snippets.

@edofic
Last active October 3, 2016 16:49
Show Gist options
  • Save edofic/36a0e8a9d9efa41a54abbe01f5004836 to your computer and use it in GitHub Desktop.
Save edofic/36a0e8a9d9efa41a54abbe01f5004836 to your computer and use it in GitHub Desktop.
Coproduct encoding of effects in Elm
port module Main exposing (..)
-- effect types
type Prompt u
= Read (String -> u)
| Write String u
type Api u
= Get Int (String -> u)
| Post String u
-- update type, to have something to target
type Update
= Set String
| Nop
-- effect representation. Putting dict should be an object of effect providers.
-- By putting a tagged product in a negative position we get a tagged sum.
type alias Eff dict cmd =
dict -> cmd
-- helper for less typing
type alias Effs dict cmd =
List (Eff dict cmd)
-- dispatch an effect
effect : (dict -> (a -> cmd)) -> a -> Eff dict cmd
effect getter a =
\dict -> getter dict a
-- effect sum can be manipulated. Eff is a contravariant functor in `dict`
modifyEffect : (newDict -> dict) -> Eff dict cmd -> Eff newDict cmd
modifyEffect f eff =
\newDict -> eff (f newDict)
-- Example effects one. Type can be inffered but is noisier
-- `e |` part is important because it signifies that this can
-- be combined with other effects
f1 : Effs { e | prompt : Prompt Update -> a } a
f1 =
[ effect .prompt <| Read Set ]
f2 : Effs { e | myapi : Api Update -> a } a
f2 =
[ effect .myapi <| Post "foo" Nop ]
-- demonstrating composition and local effect manipulation
f3 : Effs { e | api : Api Update -> a, prompt : Prompt Update -> a } a
f3 =
let
rename { api, prompt } =
{ prompt = prompt, myapi = api }
in
f1 ++ List.map (modifyEffect rename) f2
-----------------
-- this example reifies effects into a concrete ADT
type Effect u
= EPrompt (Prompt u)
| EApi (Api u)
reifyEffect : Eff { api : Api u -> Effect u, prompt : Prompt u -> Effect u } (Effect u) -> Effect u
reifyEffect eff =
eff { api = EApi, prompt = EPrompt }
-- this is a datastructure that can be introspected, for example in tests
f3r : List (Effect Update)
f3r =
List.map reifyEffect f3
----------------
-- mock implementations with ports and commands, just imagine this is the real thing
port promptRead : () -> Cmd msg
port promptWrite : String -> Cmd msg
apiGet : Int -> (String -> u) -> Cmd u
apiGet _ _ =
Cmd.none
apiPost : String -> u -> Cmd u
apiPost _ _ =
Cmd.none
realizeApi : Api u -> Cmd u
realizeApi a =
case a of
Get id cb ->
apiGet id cb
Post s cb ->
apiPost s cb
realizePrompt : Prompt u -> Cmd u
realizePrompt p =
case p of
Read cb ->
promptRead ()
Write str cb ->
promptWrite str
realizeEffect : Eff { api : Api u -> Cmd u, prompt : Prompt u -> Cmd u } (Cmd u) -> Cmd u
realizeEffect eff =
eff { api = realizeApi, prompt = realizePrompt }
-- this representation is an opaque datastructure that can be given to the elm runtime
f3real : Cmd Update
f3real =
Cmd.batch <| List.map realizeEffect f3
----------------------------------------------
-- we can also go from ADT to Cmd
realizeReified : Effect u -> Cmd u
realizeReified e =
case e of
EApi a ->
realizeApi a
EPrompt p ->
realizePrompt p
@edofic
Copy link
Author

edofic commented Oct 3, 2016

Lack of rank N types makes this a bit noisy but still usable IMO.

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