<style> .reveal .code-wrapper {width: fit-content; max-width: 200%; font-size: xxx-large;} .reveal img {max-width: 200%;} .reveal pre {max-height: 600px;} .reveal pre code {max-height: 600px; padding: 10px !important;} .reveal .hljs-ln-numbers {display: none;} code {color: #c7254e;} .machine {position: relative; left: -100px;} .actionresult {position: relative; left: -70px;} .topology {position: relative; left: -50px;} .reveal table th, .reveal table td {border-right: 1px solid; border-color: white;} .reveal table thead tr th:last-child, .reveal table tbody tr td:last-child {border-right: none;} .reveal table tr td:first-child {font-weight: bold;} .highlight {color: #d9480f} .mermaid {text-align: center !important; background-color:#2d2d2d !important;} </style> # [`Crem`](https://github.com/tweag/crem) ## Composable Representable Executable Machines <img src="https://raw.githubusercontent.com/tweag/crem/main/logo/crem-transparent.png" width="200"> --- ![the picture that explains everything](http://marcosh.github.io/img/the-picture-that-explains-everything.png) --- ### <span style="color: orange">Domain events</span> ![event](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-event.png) Events relevant for domain experts --- ### <span style="color: dodgerblue">Commands</span> ![command](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-command.png) User intentions/actions/decisions --- ### <span style="color: lightyellow">Read models</span> ![read model](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-read-model.png) Data needed in order to make decisions --- ### <span style="color: yellow">Aggregates</span> ![aggregate](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-aggregate.png) Decide what happens on commands --- ### <span style="color: orchid">Policies</span> ![policy](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-policy.png) Reactive logic that takes place after an event --- ### <span style="color: lightgreen">Projections</span> ![projection](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-projection.png) Aggregate data from events --- ## <span style="color: yellow">Aggregates</span>, <span style="color: lightgreen">projections</span> and <span style="color: orchid">policies</span> could be implemented as state machines --- ### <span style="color: yellow">Aggregates</span> ![aggregate-command-event](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-aggregate-command-event.png) From <span style="color: dodgerblue">commands</span> to <span style="color: orange">events</span> --- ### <span style="color: lightgreen">Projections</span> ![projection-event-read-model](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-projection-event-read-model.png) From <span style="color: orange">events</span> to <span style="color: lightyellow">read models</span> --- ### <span style="color: orchid">Policies</span> ![policy-event-command](https://github.com/marcosh/ddd-machines-dddeurope/raw/main/images/the-picture-that-explains-everything-policy-event-command.png) From <span style="color: orange">events</span> to <span style="color: dodgerblue">commands</span> --- ## How should we encode a state machine? --- ### Mealy machines ```haskell [1|2|3] data Mealy state input output = Mealy { initialState :: state , action :: state -> input -> (state, output) } ``` --- ### [Mealy machines](https://hackage.haskell.org/package/machines-0.7.3/docs/Data-Machine-Mealy.html#t:Mealy) ```haskell [1|2-4] newtype Mealy input output = Mealy { runMealy :: input -> (output, Mealy input output) } ``` --- ## Machines are composable --- ```haskell sequential :: Mealy a b -> Mealy b c -> Mealy a c ``` ```mermaid %%{init: {'theme':'dark'}}%% stateDiagram-v2 direction LR a --> b b --> c ``` --- ```haskell parallel :: Mealy a b -> Mealy c d -> Mealy (a, c) (b, d) ``` ```mermaid %%{init: {'theme':'dark'}}%% stateDiagram-v2 direction LR state fork <<fork>> (a,c) --> fork fork --> a fork --> c a --> b c --> d state join <<join>> b --> join d --> join join --> (b,d) ``` --- ```haskell alternative :: Mealy a b -> Mealy c d -> Mealy (Either a c) (Either c d) ``` ```mermaid %%{init: {'theme':'dark'}}%% stateDiagram-v2 direction LR eitherac: Either a c state fork <<choice>> eitherac --> fork fork --> a fork --> c a --> b c --> d state join <<choice>> b --> join d --> join join --> eitherbd eitherbd: Either b d ``` --- ```haskell feedback :: Mealy a [b] -> Mealy b [a] -> Mealy a [b] ``` ```mermaid %%{init: {'theme':'dark'}}%% stateDiagram-v2 direction LR a --> b: [] b --> a: [] ``` --- ## This encoding can only be run note: Once we defined a machine, the only way to extract information about it, is to run it, giving it inputs and observing its outputs --- ### What about documentation? --- ### What about invariant enforcement? --- ## Strengthening the type ```haskell data Machine (topology :: Topology vertex) input output ``` --- ```haskell {-# LANGUAGE DataKinds #-} newtype Topology vertex = Topology { edges :: [(vertex, [vertex])] } ``` --- ### Topology ```mermaid %%{init: {'theme':'dark'}}%% stateDiagram-v2 NoData --> CollectedUserData CollectedUserData --> CollectedLoanDetailsFirst CollectedUserData --> ReceivedCreditBureauDataFirst CollectedLoanDetailsFirst --> CollectedAllData ReceivedCreditBureauDataFirst --> CollectedAllData ``` --- ### Adding the `Topology` in the type --- Allows us to enforce execution of allowed transitions --- ```haskell [1|2|3|4-8] data Machine topology a b = forall state. Machine { initialState :: InitialState state , action :: forall initialVertex . state initialVertex -> a -> ActionResult topology state initialVertex b } ``` <!-- .element: class="machine" --> --- ```haskell [1|2|4|3] data ActionResult topology state initial b where ActionResult :: AllowedTransition topology initial final => (b, state final) -> ActionResult topology state initial b ``` <!-- .element: class="actionresult" --> --- Allows us to retrieve information about our state machine without running it --- ```haskell [1|7-8|9|3-5] topology :: forall vertex topology a b . ( Demote vertex ~ vertex , SingKind vertex , SingI topology ) => Machine (topology :: Topology vertex) a b -> Topology vertex baseMachineTopology _ = demote @topology ``` <!-- .element: class="topology" --> --- ### But... --- ### Composition becomes harder ```haskell sequential :: Machine t1 a b -> Machine t2 b c -> Machine ??? a c ``` --- Requires computation at the type level --- Breaks usage of standard typeclasses like `Arrow` or `Category` --- ## Can we get the best of both worlds? --- ```haskell data StateMachine input output where ``` --- ```haskell [1|2-4] data StateMachine input output where Basic :: Machine topology input output -> StateMachine input output ``` --- ```haskell [1|2-3] foo :: StateMachine input output -> a foo stateMachine = case stateMachine of Basic machine -> _ ``` --- ```haskell [3-6] data StateMachine input output where ... Sequential :: StateMachine a b -> StateMachine b c -> StateMachine a c ``` --- ```haskell [3-6] data StateMachine input output where ... Parallel :: StateMachine a b -> StateMachine c d -> StateMachine (a, c) (b, d) ``` --- ```haskell [3-6] data StateMachine input output where ... Alternative :: StateMachine a b -> StateMachine c d -> StateMachine (Either a c) (Either b d) ``` --- ```haskell [3-6] data StateMachine input output where ... Feedback :: StateMachine a [b] -> StateMachine b [a] -> StateMachine a [b] ``` --- ```mermaid %%{init: {'theme':'dark'}}%% graph TD A[Alternative] --> B[Parallel] A --> C[Sequential] B --> D[Machine 1] B --> E[Machine 2] C --> F[Machine 3] C --> G[Machine 4] ``` --- ## Composable --- ```haskell [1|2|4] instance Category StateMachine where id = Basic identity (.) = flip Sequential ``` --- ```haskell [1|2] instance Strong StateMachine where second' = Parallel id ``` --- ```haskell [1|2] instance Choice StateMachine where right' = Alternative id ``` --- ## Executable --- ```haskell run :: StateMachine a b -> a -> (b, StateMachine a b) ``` --- ```haskell [1|2-3] run (Basic machine) input = _ -- use the `action` -- with `initialState` and `input` ``` --- ```haskell [1|3-4|5-6|8-9] run (Sequential machine1 machine2) input = let (output1, machine1') = run machine1 input (output2, machine2') = run machine2 output1 in ( output2 , Sequential machine1' machine2' ) ``` --- ```haskell [1|3-4|5-6|8-9] run (Parallel machine1 machine2) input = let (output1, machine1') = run machine1 (fst input) (output2, machine2') = run machine2 (snd input) in ( (output1, output2) , Parallel machine1' machine2' ) ``` --- ## Representable --- ```haskell [1-2|4] newtype Mermaid = Mermaid {getText :: Text} render :: StateMachine a b -> Mermaid ``` --- ```haskell [1|2] render (Basic machine) = renderTopology (topology machine) ``` --- ```haskell [1|3|4|6-8] render (Sequential machine1 machine2) = let mermaid1 = render machine1 mermaid2 = render machine2 in mermaid1 <> Mermaid " --> " <> mermaid2 ``` --- ### We can draw our machines --- <img alt="risk manager" src="https://mermaid.ink/svg/pako:eNqlVF1PwjAU_SvkPmkyCGCBbQ8mCvqkxkj0wSwhzXad1a4lXUdAsv9u9-HcCMgMfepOz73n3PbubsGXAYILsaYaZ4yGikbd1dATHbNysEPDUGGY7bYFnK0KXDzIGdX0BZXG9b7zqeQcfY3Bc4yqJfVOUjFDTRmPb5mK9eGQJ_SRrTCYKgyYvk4U0iQTORJWKV1x_renenmdbvfyxMqa1EMJ29ffLvGpt7TfzyH3R--0hZ1_5E7rzbqUnPmbeqcWyOLsvMH-bepMqOAUJ2WGhn5DQckPY4VJ0VCp0MWciZDjYwXs8bqjXlHrOpEvhU91XaSEdmupOcrSlSywIEIVURaY3ztP4oF-xwg9cM02oOrTA0-khpcsA6N4Y95CKnDfKI_RAppoOd8IH1ytEvwhlSOiYi2peJWy8Q3uFtbgkv6oN7ZtZ0xscjEZWbABdziY9IjjODaZkMFw0HdIasFXHt_vGQrmFu6LkZRPpvQbS36R_g" height="600"> --- ## Demo time ![the hobbit](https://ready64.org/giochi/full/h/hobbit_01.png) [The Hobbit code](https://github.com/tweag/crem/blob/main/examples/Crem/Example/TheHobbit.hs) [The Hobbit map](https://mermaid.live/edit#pako:eNp9ksFuwjAMhl-l8rlFUNrS5rDLNmkHkKZtpykXi3gQkTqoTdEY4t0Xyqhox3aKrf-L_Sf2AZZWEQioHTp60LiqsIx2sWTJbw0zmbne0BMaI3lufYqsai9V1pj63hBWmleSX_SOWNGJWuja7Re2YYeaL-QzunV3C3c0LB5E0V1wVb8LW-FPI2d14KWft8iVvRvqtcWOvFm6rw5e2ktvtz31-MfzUD5_VBf_MgshlFSVqJUf4EFyEEhwaypJgvChwmojQfLRc81W-fk-Ku1sBeIDTU0hYOPs656XIFzV0AX6WYKO2iK_W9vLQRzgE0RUTIpsNB4XsyzN4jSEPYikiEdpPomLPI_9kR5D-GpvF6NZkuTTdDpJsmk-G3ucWjuL8wK2e3j8Bgbo5M8) note: `cabal run hobbit-game` `cabal run hobbit-map` --- ## That's all! --- ## Questions and feedback :scroll: [marcosh.github.io](http://marcosh.github.io/) :bird: [@marcoshuttle](https://twitter.com/marcoshuttle) :elephant: [@marcosh@functional.cafe](https://functional.cafe/@marcosh)
{"metaMigratedAt":"2023-06-18T01:32:27.752Z","metaMigratedFrom":"YAML","title":"crem","breaks":true,"slideOptions":"{\"progress\":true,\"controls\":false,\"slideNumber\":false}","contributors":"[{\"id\":\"0893b955-bc9c-4ecc-b38c-6072b5bc1ecd\",\"add\":12818,\"del\":1284}]"}
    574 views