Last active
October 3, 2016 16:49
-
-
Save edofic/36a0e8a9d9efa41a54abbe01f5004836 to your computer and use it in GitHub Desktop.
Coproduct encoding of effects 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
| 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lack of rank N types makes this a bit noisy but still usable IMO.