Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Maintainable Software Architecture in Haskell

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Maintainable Software Architecture in Haskell

This talk is perfect for Developers who are interested in seeing Haskell being used as a general programming language. Engineers who are hoping to see Haskell as an environment in which they can quickly and effectively iterate between requirements, design and running executable code: providing value to the business with an immediate feedback loop.

Avatar for Signify

Signify

May 28, 2020
Tweet

More Decks by Signify

Other Decks in Technology

Transcript

  1. maintain [ meyn-teyn ] verb (used with object) 1. to

    keep in existence 2. to keep in an appropriate condi2on, opera2on, or force; keep unimpaired: 3. to keep in a specified state, posi2on, etc. © Pawel Szulc, @EncodePanda, [email protected] 2
  2. maintain [ meyn-teyn ] verb (used with object) 1. to

    keep in existence 2. to keep in an appropriate condi.on, opera/on, or force; keep unimpaired: 3. to keep in a specified state, posi/on, etc. © Pawel Szulc, @EncodePanda, [email protected] 3
  3. maintain [ meyn-teyn ] verb (used with object) 1. to

    keep in existence 2. to keep in an appropriate condi.on, opera/on, or force; keep unimpaired: 3. to keep in a specified state, posi/on, etc. ... in Haskell? © Pawel Szulc, @EncodePanda, [email protected] 4
  4. "Socialism Haskell is a system language which heroically overcomes difficul9es

    unknown in any other system language" © Pawel Szulc, @EncodePanda, [email protected] 6
  5. Our plan for today 1. Coding Dojo / Hack day

    2. Real world example • problem • approach • consequences • Polysemy © Pawel Szulc, @EncodePanda, [email protected] 10
  6. "As a Billing System user I want to generate an

    invoice for a given account based on its current system use" © Pawel Szulc, @EncodePanda, [email protected] 14
  7. Func%ons and their nature 1. Manipulate data (f :: Input

    -> Output) 2. Interact with an outside world © Pawel Szulc, @EncodePanda, [email protected] 15
  8. doStuff :: Int -> Int doStuff i = i +

    1 Why this func,on is soooo good? • easy to test • you will be no/fied if its behavior changes © Pawel Szulc, @EncodePanda, [email protected] 16
  9. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 18
  10. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 19
  11. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 20
  12. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 21
  13. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 22
  14. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 23
  15. It's easy to test and maintain func1on if it only

    manipulates data. Can we change "interac.ons with the outside world" into data? © Pawel Szulc, @EncodePanda, [email protected] 24
  16. -- | take an Int (i) and UUID (uuid) as

    parameters -- | fetch existing Int under given uuid from MongoDB -- | (if does not exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> IO String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 25
  17. -- | take Int, return +1 as text doStuff ::

    Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, [email protected] 27
  18. prop_returns_plus1 :: Property prop_returns_plus1 = property do -- given i

    <- Gen.int -- when let res = doStuff i -- then res === "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, [email protected] 28
  19. module Main where main :: IO () main = putStrLn

    $ doStuff 10 © Pawel Szulc, @EncodePanda, [email protected] 29
  20. -- | take Int, return +1 as text doStuff ::

    Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, [email protected] 30
  21. -- | take Int, return +1 as text doStuff ::

    Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, [email protected] 31
  22. -- | take Int, store it, return +1 as text

    doStuff :: UUID -> Int -> String doStuff i = ... © Pawel Szulc, @EncodePanda, [email protected] 32
  23. data Storage = Persist UUID Int -- | take Int,

    store it, return +1 as text doStuff :: UUID -> Int -> (Storage, String) doStuff uuid i = ( Persist uuid newI , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 43
  24. data Storage = Persist UUID Int -- | take Int,

    store it, return +1 as text doStuff :: UUID -> Int -> (Storage, String) doStuff uuid i = ( Persist uuid newI , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 44
  25. data Storage = Persist UUID Int -- | take Int,

    store it, return +1 as text doStuff :: UUID -> Int -> (Storage, String) doStuff uuid i = ( Persist uuid newI , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 45
  26. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 46
  27. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 47
  28. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 48
  29. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 49
  30. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 50
  31. doStuff :: UUID -> Int -> (Storage, String) interpret ::

    (Storage, String) -> IO String © Pawel Szulc, @EncodePanda, [email protected] 53
  32. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 54
  33. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 55
  34. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 56
  35. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 57
  36. main :: IO () main = do ioRef <- newIORef

    M.empty uuid <- nextRandom res <- interpret ioRef (doStuff uuid 10) putStrLn res © Pawel Szulc, @EncodePanda, [email protected] 58
  37. -- | take Int, store it, return +1 as text

    doStuff :: UUID -> Int -> (Storage, String) doStuff uuid i = ( Persist uuid newI , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 59
  38. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> (Storage, String) doStuff uuid i = ( Persist uuid newI , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 60
  39. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> ([Storage], String) doStuff uuid i = ( [(Persist uuid newI)] , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 61
  40. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 66
  41. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> (Storage, String) -> IO String interpret ioRef (Persist uuid pi, i) = do modifyIORef ioRef (M.insert uuid pi) return i © Pawel Szulc, @EncodePanda, [email protected] 67
  42. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 68
  43. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 69
  44. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 70
  45. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 71
  46. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 72
  47. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 73
  48. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( Persist uuid (i + 1) , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 74
  49. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( [Persist uuid (i + 1)] , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 75
  50. main :: IO () main = do ioRef <- newIORef

    M.empty uuid <- nextRandom res <- interpret ioRef (doStuff uuid 10) putStrLn res © Pawel Szulc, @EncodePanda, [email protected] 76
  51. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> ([Storage], String) doStuff uuid i = ( [(Persist uuid newI)] , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 77
  52. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> ([Storage], String) doStuff uuid i = ( [ (Persist uuid newI) , (Persist uuid newI) ] , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 78
  53. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( [Persist uuid (i + 1)] , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 79
  54. prop_returns_plus1 :: Property prop_returns_plus1 = property $ do -- given

    i <- Gen.int uuid <- genUUID -- when let result = doStuff uuid i -- then let expected = ( [ Persist uuid (i + 1) , Persist uuid (i + 1)] , "New value: " ++ (show $ i + 1) ) result === expected © Pawel Szulc, @EncodePanda, [email protected] 80
  55. doStuff :: UUID -> Int -> ([Storage], String) interpret ::

    ([Storage], String) -> IO String © Pawel Szulc, @EncodePanda, [email protected] 82
  56. sthElse :: UUID -> Int -> ([Storage], Int) interpret ::

    ([Storage], String) -> IO String © Pawel Szulc, @EncodePanda, [email protected] 83
  57. sthElse :: UUID -> Int -> ([Storage], Int) interpret ::

    ([Storage], a) -> IO a © Pawel Szulc, @EncodePanda, [email protected] 84
  58. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], String) -> IO String interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 85
  59. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> ([Storage], a) -> IO a interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 86
  60. data Storage k = Persist UUID Int deriving stock (Eq,

    Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> ([Storage], String) doStuff uuid i = ( [ (Persist uuid newI) , (Persist uuid newI)] , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 87
  61. data Storage k = Persist UUID Int deriving stock (Eq,

    Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> ([Storage], String) doStuff uuid i = ( [ (Persist uuid newI) , (Persist uuid newI)] , "New value: " ++ (show newI) ) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 88
  62. data Storage k = Done k | Persist UUID Int

    deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI))] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 89
  63. data Storage k = Done k | Persist UUID Int

    deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI))] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 90
  64. data Storage k = Done k | Persist UUID Int

    deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI))] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 91
  65. data Storage k = Done k | Persist UUID Int

    deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI))] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 92
  66. interpret :: IORef InMemStorage -> ([Storage], a) -> IO a

    interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 93
  67. interpret :: IORef InMemStorage -> ([Storage], a) -> IO a

    interpret ioRef (actions, i) = do traverse perform actions return i where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) © Pawel Szulc, @EncodePanda, [email protected] 94
  68. interpret :: IORef InMemStorage -> [Storage a] -> IO a

    interpret ioRef actions = do traverse perform (init actions) value (last actions) where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) value (Done a) = pure a value _ = fail "failed" © Pawel Szulc, @EncodePanda, [email protected] 95
  69. interpret :: IORef InMemStorage -> [Storage a] -> IO a

    interpret ioRef actions = do traverse perform (init actions) value (last actions) where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) value (Done a) = pure a value _ = fail "failed" © Pawel Szulc, @EncodePanda, [email protected] 96
  70. interpret :: IORef InMemStorage -> [Storage a] -> IO a

    interpret ioRef actions = do traverse perform (init actions) value (last actions) where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) value (Done a) = pure a value _ = fail "failed" © Pawel Szulc, @EncodePanda, [email protected] 97
  71. interpret :: IORef InMemStorage -> [Storage a] -> IO a

    interpret ioRef actions = do traverse perform (init actions) value (last actions) where perform (Persist uuid pi) = modifyIORef ioRef (M.insert uuid pi) value (Done a) = pure a value _ = fail "failed" © Pawel Szulc, @EncodePanda, [email protected] 98
  72. data Storage k = Done k | Persist UUID Int

    deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI)) ] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 99
  73. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI)) ] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 100
  74. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> [Storage String] doStuff uuid i = [ (Persist uuid newI) , (Persist uuid newI) , (Done $ "New value: " ++ (show newI)) ] where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 101
  75. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 102
  76. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 103
  77. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 104
  78. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 105
  79. interpret :: IORef InMemStorage -> Storage a -> IO a

    interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = modifyIORef ioRef (M.insert uuid i) *> interpret ioRef next © Pawel Szulc, @EncodePanda, [email protected] 106
  80. interpret :: IORef InMemStorage -> Storage a -> IO a

    interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = modifyIORef ioRef (M.insert uuid i) *> interpret ioRef next © Pawel Szulc, @EncodePanda, [email protected] 107
  81. interpret :: IORef InMemStorage -> Storage a -> IO a

    interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = modifyIORef ioRef (M.insert uuid i) *> interpret ioRef next © Pawel Szulc, @EncodePanda, [email protected] 108
  82. interpret :: IORef InMemStorage -> Storage a -> IO a

    interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = modifyIORef ioRef (M.insert uuid i) *> interpret ioRef next © Pawel Szulc, @EncodePanda, [email protected] 111
  83. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) -- | take Int, store it once, story it twice, return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 112
  84. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Eq, Show) © Pawel Szulc, @EncodePanda, [email protected] 113
  85. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Functor, Eq, Show) © Pawel Szulc, @EncodePanda, [email protected] 114
  86. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Functor, Eq, Show) instance Applicative Storage where pure a = Done a (<*>) func (Done a) = fmap (\f -> f a) func (<*>) func (Persist uuid i next) = Persist uuid i (func <*> next) © Pawel Szulc, @EncodePanda, [email protected] 115
  87. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = (Persist uuid newI (Persist uuid newI (Done $ "New value: " ++ (show newI)) )) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 116
  88. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = Persist uuid newI (Done ()) *> Persist uuid newI (Done ()) *> pure ("New value: " ++ (show newI)) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 117
  89. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = Persist uuid newI (Done ()) *> Persist uuid newI (Done ()) *> pure ("New value: " ++ (show newI)) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 118
  90. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = Persist uuid newI (Done ()) *> Persist uuid newI (Done ()) *> pure ("New value: " ++ (show newI)) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 119
  91. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = Persist uuid newI (Done ()) *> Persist uuid newI (Done ()) *> pure ("New value: " ++ (show newI)) where newI = i + 1 © Pawel Szulc, @EncodePanda, [email protected] 120
  92. -- | take Int, store it once, story it twice,

    return +1 as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = persist uuid newI *> persist uuid newI *> pure ("New value: " ++ (show newI)) where newI = i + 1 persist :: UUID -> Int -> Storage () persist uuid i = Persist uuid i (Done ()) © Pawel Szulc, @EncodePanda, [email protected] 121
  93. -- | take Int, fetch existing Int (if does not

    exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = fetch uuid *> persist ... data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID ... deriving stock (Functor) fetch :: UUID -> Storage (Maybe Int) © Pawel Szulc, @EncodePanda, [email protected] 122
  94. -- | take Int, fetch existing Int (if does not

    exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = fetch uuid *> persist ... data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID ... deriving stock (Functor) fetch :: UUID -> Storage (Maybe Int) © Pawel Szulc, @EncodePanda, [email protected] 123
  95. -- | take Int, fetch existing Int (if does not

    exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = fetch uuid *> persist ... data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID ... deriving stock (Functor) fetch :: UUID -> Storage (Maybe Int) © Pawel Szulc, @EncodePanda, [email protected] 124
  96. -- | take Int, fetch existing Int (if does not

    exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = fetch uuid *> persist ... data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID ... deriving stock (Functor) fetch :: UUID -> Storage (Maybe Int) © Pawel Szulc, @EncodePanda, [email protected] 125
  97. -- | take Int, fetch existing Int (if does not

    exist, default to zero) -- | add them, store the result, return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = fetch uuid *> persist ... data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID ... deriving stock (Functor) fetch :: UUID -> Storage (Maybe Int) © Pawel Szulc, @EncodePanda, [email protected] 126
  98. data Storage k = Done k | Persist UUID Int

    (Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, [email protected] 127
  99. data Storage k = Done k | Persist UUID Int

    (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, [email protected] 128
  100. data Storage k = Done k | Persist UUID Int

    (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) persist :: UUID -> Int -> Storage () persist uuid i = Persist uuid i (Done ()) fetch :: UUID -> Storage (Maybe Int) fetch uuid = Fetch uuid (\mi -> Done mi) © Pawel Szulc, @EncodePanda, [email protected] 129
  101. data Storage k = Done k | Persist UUID Int

    (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) persist :: UUID -> Int -> Storage () persist uuid i = Persist uuid i (Done ()) fetch :: UUID -> Storage (Maybe Int) fetch uuid = Fetch uuid pure © Pawel Szulc, @EncodePanda, [email protected] 130
  102. "Sequen'ally compose two ac'ons, passing any value produced by the

    first as an argument to the second." © Pawel Szulc, @EncodePanda, [email protected] 131
  103. ??? :: m a -> (a -> m b) ->

    m b © Pawel Szulc, @EncodePanda, [email protected] 132
  104. >>= :: m a -> (a -> m b) ->

    m b © Pawel Szulc, @EncodePanda, [email protected] 133
  105. instance Monad Storage where (Done a) >>= f = f

    a (Persist uuid i next) >>= f = Persist uuid i (next >>= f) (Fetch uuid nextFunc) >>= f = Fetch uuid (\mi -> (nextFunc mi) >>= f) © Pawel Szulc, @EncodePanda, [email protected] 134
  106. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 136
  107. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 137
  108. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 138
  109. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 139
  110. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 140
  111. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 141
  112. -- | take Int, fetch existing Int -- | (if

    does not exist, default to zero) -- | add them, -- | store the result, -- | return result as text doStuff :: UUID -> Int -> Storage String doStuff uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 142
  113. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 143
  114. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 144
  115. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 145
  116. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 146
  117. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 147
  118. type InMemStorage = M.Map UUID Int interpret :: IORef InMemStorage

    -> Storage a -> IO a interpret ioRef (Done a) = pure a interpret ioRef (Persist uuid i next) = (modifyIORef ioRef (M.insert uuid i)) *> (interpret ioRef next) interpret ioRef (Fetch uuid nextFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem interpret ioRef (nextFunc maybeI) © Pawel Szulc, @EncodePanda, [email protected] 148
  119. prop_fetch_add_store_return :: Property prop_fetch_add_store_return = property $ do -- given

    i <- Gen.int uuid <- genUUID initial <- Gen.int ioRef <- evalIO $ newIORef $ M.singleton uuid initial -- when res <- evalIO $ interpret ioRef (doStuff uuid i) -- then inmem <- evalIO $ readIORef ioRef res === "New value: " ++ show (i + initial) M.toList inmem === [(uuid, i + initial)] © Pawel Szulc, @EncodePanda, [email protected] 149
  120. doStuff :: UUID -> Int -> IO String doStuff uuid

    i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 150
  121. doStuff :: UUID -> Int -> Storage String doStuff uuid

    i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 151
  122. data Storage k = Done k | Persist UUID Int

    (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) instance Applicative Storage where pure a = Done a (<*>) func (Done a) = fmap (\f -> f a) func (<*>) func (Persist uuid i next) = Persist uuid i (func <*> next) instance Monad Storage where (Done a) >>= f = f a (Persist uuid i next) >>= f = Persist uuid i (next >>= f) (Fetch uuid nextFunc) >>= f = Fetch uuid (\mi -> (nextFunc mi) >>= f) © Pawel Szulc, @EncodePanda, [email protected] 152
  123. data Storage k = Done k | Persist UUID Int

    (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, [email protected] 154
  124. data Storage k = Persist UUID Int k | Fetch

    UUID (Maybe Int -> k) deriving stock (Functor) data Free (f:: * -> *) (k :: *) = Pure k | Impure (f (Free f k)) © Pawel Szulc, @EncodePanda, [email protected] 155
  125. data Storage k = Persist UUID Int k | Fetch

    UUID (Maybe Int -> k) deriving stock (Functor) data Free (f:: * -> *) (k :: *) = Pure k | Impure (f (Free f k)) persist :: UUID -> Int -> Free Storage () persist uuid i = Impure (Persist uuid i (Pure ())) fetch :: UUID -> Free Storage (Maybe Int) fetch uuid = Impure (Fetch uuid (\mi -> Pure mi)) © Pawel Szulc, @EncodePanda, [email protected] 156
  126. instance Functor f => Functor (Free f) where fmap f

    (Pure k) = Pure $ f k fmap f (Impure c) = Impure (fmap (fmap f) c) instance Functor f => Applicative (Free f) where pure a = Pure a (<*>) func (Pure a) = fmap (\f -> f a) func (<*>) func (Impure c) = Impure (fmap (\f -> func <*> f) c) instance Functor f => Monad (Free f) where Pure k >>= f = f k Impure c >>= f = Impure $ fmap (\x -> x >>= f) c © Pawel Szulc, @EncodePanda, [email protected] 157
  127. data Storage k = Persist UUID Int k | Fetch

    UUID (Maybe Int -> k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, [email protected] 158
  128. doStuff :: UUID -> Int -> Storage String doStuff uuid

    i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 159
  129. doStuff :: UUID -> Int -> Free Storage String doStuff

    uuid i = do maybeOld <- fetch uuid let oldI = maybe 0 id maybeOld newI = oldI + i persist uuid newI pure ("New value: " ++ (show newI)) © Pawel Szulc, @EncodePanda, [email protected] 160
  130. interpretFree :: Monad m => (forall x. f x ->

    m x) -> Free f a -> m a interpretFree _ (Pure a) = pure a interpretFree f (Impure c) = f c >>= interpretFree f © Pawel Szulc, @EncodePanda, [email protected] 161
  131. interpretFree :: Monad m => (forall x. f x ->

    m x) -> Free f a -> m a interpretFree _ (Pure a) = pure a interpretFree f (Impure c) = f c >>= interpretFree f interpret :: IORef InMemStorage -> Storage a -> IO a interpret ioRef (Persist uuid i k) = do modifyIORef ioRef (M.insert uuid i) pure k interpret ioRef (Fetch uuid kFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem pure $ kFunc maybeI © Pawel Szulc, @EncodePanda, [email protected] 162
  132. interpretFree :: Monad m => (forall x. f x ->

    m x) -> Free f a -> m a interpretFree _ (Pure a) = pure a interpretFree f (Impure c) = f c >>= interpretFree f interpret :: IORef InMemStorage -> Storage a -> IO a interpret ioRef (Persist uuid i k) = do modifyIORef ioRef (M.insert uuid i) pure k interpret ioRef (Fetch uuid kFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem pure $ kFunc maybeI © Pawel Szulc, @EncodePanda, [email protected] 163
  133. interpretFree :: Monad m => (forall x. f x ->

    m x) -> Free f a -> m a interpretFree _ (Pure a) = pure a interpretFree f (Impure c) = f c >>= interpretFree f interpret :: IORef InMemStorage -> Storage a -> IO a interpret ioRef (Persist uuid i k) = do modifyIORef ioRef (M.insert uuid i) pure k interpret ioRef (Fetch uuid kFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem pure $ kFunc maybeI © Pawel Szulc, @EncodePanda, [email protected] 164
  134. interpretFree :: Monad m => (forall x. f x ->

    m x) -> Free f a -> m a interpretFree _ (Pure a) = pure a interpretFree f (Impure c) = f c >>= interpretFree f interpret :: IORef InMemStorage -> Storage a -> IO a interpret ioRef (Persist uuid i k) = do modifyIORef ioRef (M.insert uuid i) pure k interpret ioRef (Fetch uuid kFunc) = do inmem <- readIORef ioRef let maybeI = M.lookup uuid inmem pure $ kFunc maybeI © Pawel Szulc, @EncodePanda, [email protected] 165
  135. interpretFree :: (f x -> m x) -> Free f

    a -> m a interpret :: IORef InMemStorage -> Storage a -> IO a © Pawel Szulc, @EncodePanda, [email protected] 166
  136. prop_fetch_add_store_return :: Property prop_fetch_add_store_return = property $ do -- given

    i <- Gen.int uuid <- genUUID initial <- Gen.int ioRef <- evalIO $ newIORef $ M.singleton uuid initial -- when res <- evalIO $ interpret ioRef (doStuff uuid i) -- then inmem <- evalIO $ readIORef ioRef res === "New value: " ++ show (i + initial) M.toList inmem === [(uuid, i + initial)] © Pawel Szulc, @EncodePanda, [email protected] 167
  137. prop_fetch_add_store_return :: Property prop_fetch_add_store_return = property $ do -- given

    i <- Gen.int uuid <- genUUID initial <- Gen.int ioRef <- evalIO $ newIORef $ M.singleton uuid initial -- when res <- evalIO $ interpret ioRef (doStuff uuid i) -- then inmem <- evalIO $ readIORef ioRef res === "New value: " ++ show (i + initial) M.toList inmem === [(uuid, i + initial)] © Pawel Szulc, @EncodePanda, [email protected] 168
  138. prop_fetch_add_store_return :: Property prop_fetch_add_store_return = property $ do -- given

    i <- Gen.int uuid <- genUUID initial <- Gen.int ioRef <- evalIO $ newIORef $ M.singleton uuid initial -- when res <- evalIO $ interpretFree (interpret ioRef) (doStuff uuid i) -- then inmem <- evalIO $ readIORef ioRef res === "New value: " ++ show (i + initial) M.toList inmem === [(uuid, i + initial)] © Pawel Szulc, @EncodePanda, [email protected] 169
  139. "As a Billing System user I want to generate an

    invoice for a given account based on its current system use" © Pawel Szulc, @EncodePanda, [email protected] 171
  140. To generate invoice for account account_id 1. fetch profile from

    CRM 2. fetch CDRs from FTP 3. generate invoice number 4. total = sum cdrs 5. glue together © Pawel Szulc, @EncodePanda, [email protected] 186
  141. Sem r a ___ program :: Sem '[Console, (Random Int)]

    Int ___ .________________________________________^ © Pawel Szulc, @EncodePanda, [email protected] 192
  142. Sem r a ________________________ program :: Sem '[Console, (Random Int)]

    Int ________________________ .________________________^ © Pawel Szulc, @EncodePanda, [email protected] 194
  143. Sem r a program :: Member Console r => Member

    (Random Int) r => Sem r Int . © Pawel Szulc, @EncodePanda, [email protected] 196
  144. Sem r a program :: Member Console r => Member

    (Random Int) r => Sem r Int .________^ © Pawel Szulc, @EncodePanda, [email protected] 197
  145. Sem r a program :: Member Console r <| =>

    Member (Random Int) r <| => Sem r Int | .________^_____________________| © Pawel Szulc, @EncodePanda, [email protected] 198
  146. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 199
  147. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 200
  148. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 201
  149. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 202
  150. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 203
  151. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 204
  152. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 205
  153. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Console m a where PrintLine :: String -> Console m () ReadLine :: Console m String makeSem ''Console printLine :: Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String © Pawel Szulc, @EncodePanda, [email protected] 206
  154. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Random v m a where NextRandom :: Random v m v makeSem ''Random nextRandom :: Member (Random v) r => Sem r v © Pawel Szulc, @EncodePanda, [email protected] 207
  155. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Random v m a where NextRandom :: Random v m v makeSem ''Random nextRandom :: Member (Random v) r => Sem r v © Pawel Szulc, @EncodePanda, [email protected] 208
  156. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Random v m a where NextRandom :: Random v m v makeSem ''Random nextRandom :: Member (Random v) r => Sem r v © Pawel Szulc, @EncodePanda, [email protected] 209
  157. program :: Member Console r => Member (Random Int) r

    => Sem r Int data Random v m a where NextRandom :: Random v m v makeSem ''Random nextRandom :: Member (Random v) r => Sem r v © Pawel Szulc, @EncodePanda, [email protected] 210
  158. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 211
  159. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 212
  160. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 213
  161. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 214
  162. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 215
  163. -- cheatsheet printLine :: Member Console r => String ->

    Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @EncodePanda, [email protected] 216
  164. program :: Member Console r => Member (Random Int) r

    => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) -- Sem r a ~> IO a ? © Pawel Szulc, @EncodePanda, [email protected] 217
  165. run :: Sem '[] a -> a runM :: Monad

    m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @EncodePanda, [email protected] 218
  166. run :: Sem '[] a -> a runM :: Monad

    m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @EncodePanda, [email protected] 219
  167. run :: Sem '[] a -> a runM :: Monad

    m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @EncodePanda, [email protected] 220
  168. run :: Sem '[] a -> a runM :: Monad

    m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @EncodePanda, [email protected] 221
  169. run :: Sem '[] a -> a runM :: Monad

    m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @EncodePanda, [email protected] 222
  170. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 224
  171. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 225
  172. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 226
  173. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 227
  174. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 228
  175. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 229
  176. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String -- embed :: Member (Embed m) r => m a -> Sem r a runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> putStrLn line ReadLine -> getLine © Pawel Szulc, @EncodePanda, [email protected] 230
  177. data Console m a where PrintLine :: String -> Console

    m () ReadLine :: Console m String -- embed :: Member (Embed m) r => m a -> Sem r a runConsoleIO :: Member (Embed IO) r => Sem (Console ': r) a -> Sem r a runConsoleIO = interpret $ \case PrintLine line -> embed $ putStrLn line ReadLine -> embed $ getLine © Pawel Szulc, @EncodePanda, [email protected] 231
  178. data Random v m a where NextRandom :: Random v

    m v runRandomIO :: Member (Embed IO) r => Sem (Random Int ': r) a -> Sem r a runRandomIO = interpret $ \case NextRandom -> embed randomIO © Pawel Szulc, @EncodePanda, [email protected] 232
  179. data Random v m a where NextRandom :: Random v

    m v runRandomIO :: Member (Embed IO) r => Sem (Random Int ': r) a -> Sem r a runRandomIO = interpret $ \case NextRandom -> embed randomIO © Pawel Szulc, @EncodePanda, [email protected] 233
  180. main :: IO () main = execute >>= putStrLn.show where

    execute = program & runConsoleIO & runRandomIO & runM © Pawel Szulc, @EncodePanda, [email protected] 234
  181. main :: IO () main = execute >>= putStrLn.show where

    execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO & runRandomIO & runM © Pawel Szulc, @EncodePanda, [email protected] 235
  182. main :: IO () main = execute >>= putStrLn.show where

    execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO -- Sem '[ , Random Int, Embed IO] Int & runRandomIO & runM © Pawel Szulc, @EncodePanda, [email protected] 236
  183. main :: IO () main = execute >>= putStrLn.show where

    execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO -- Sem '[ , Random Int, Embed IO] Int & runRandomIO -- Sem '[ , Embed IO] Int & runM © Pawel Szulc, @EncodePanda, [email protected] 237
  184. main :: IO () main = execute >>= putStrLn.show where

    execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO -- Sem '[ , Random Int, Embed IO] Int & runRandomIO -- Sem '[ , Embed IO] Int & runM -- IO Int © Pawel Szulc, @EncodePanda, [email protected] 238
  185. data Invoice = Invoice { invoiceNumber :: InvoiceNumber , fullName

    :: FullName , deliveryAddress :: Address , total :: Cent } © Pawel Szulc, @EncodePanda, [email protected] 240
  186. newtype InvoiceNumber = InvoiceNumber { unInvoiceNumber :: Text } deriving

    (Show, Eq) data Address = Address { street :: Text , house :: Text , num :: Text , city :: Text , country :: Text } data FullName = FullName { first :: Text , last :: Text } © Pawel Szulc, @EncodePanda, [email protected] 241
  187. data Invoice = Invoice { invoiceNumber :: InvoiceNumber , fullName

    :: FullName , deliveryAddress :: Address , total :: Cent } © Pawel Szulc, @EncodePanda, [email protected] 242
  188. data CallType = Voice | Sms newtype Duration = Duration

    { unDuration :: Int } deriving stock (Show, Eq) deriving newtype (Num) data Cdr = Cdr { uuid :: UUID , accountId :: AccountId , callType :: CallType , callDuration :: Duration } © Pawel Szulc, @EncodePanda, [email protected] 243
  189. data Plan = Plan { voiceCost :: Cent , smsCost

    :: Cent } data Profile = Profile { firstName :: Text , lastName :: Text , address :: Address , plan :: Plan } © Pawel Szulc, @EncodePanda, [email protected] 244
  190. mkInvoice :: InvoiceNumber -> Profile -> [Cdr] -> Invoice mkInvoice

    invNum Profile {..} cdrs = Invoice { invoiceNumber = invNum , fullName = FullName firstName lastName , deliveryAddress= address , total = foldr cost zeroCents cdrs } where cost (Cdr _ _ Voice (Duration duration)) acc = acc + (voiceCost plan * duration) cost (Cdr _ _ Sms (Duration amount)) acc = acc + (smsCost plan * amount) © Pawel Szulc, @EncodePanda, [email protected] 245
  191. import Polysemy data Crm m a where GetProfile :: AccountId

    -> Crm m Profile makeSem ''Crm © Pawel Szulc, @EncodePanda, [email protected] 250
  192. import Polysemy data CdrStore m a where FetchCdrs :: AccountId

    -> CdrStore m [Cdr] makeSem ''CdrStore © Pawel Szulc, @EncodePanda, [email protected] 254
  193. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId

    -> InvoiceStore m InvoiceNumber makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, [email protected] 258
  194. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId

    -> InvoiceStore m InvoiceNumber makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, [email protected] 262
  195. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId

    -> InvoiceStore m InvoiceNumber StoreInvoice :: AccountId -> Invoice -> InvoiceStore m () makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, [email protected] 263
  196. generateInvoice :: Member CdrStore r => Member Crm r =>

    Member InvoiceStore r => AccountId -> Sem r Invoice generateInvoice accId = do invNumber <- genNextInvoiceNumber accId profile <- getProfile accId cdrs <- fetchCdrs accId let invoice = mkInvoice invNumber profile cdrs storeInvoice accId invoice return invoice © Pawel Szulc, @EncodePanda, [email protected] 266
  197. data Crm m a where GetProfile :: AccountId -> Crm

    m Profile type CrmMap = M.Map AccountId Profile runCrm :: Member (State CrmMap) r => Sem (Crm ': r) a -> Sem r a runCrm = interpret $ \case GetProfile accountId -> gets (\m -> m M.! accountId) © Pawel Szulc, @EncodePanda, [email protected] 267
  198. data CdrStore m a where FetchCdrs :: AccountId -> CdrStore

    m [Cdr] type CdrMap = M.Map AccountId [Cdr] runCdrStore :: Member (State CdrMap) r => Sem (CdrStore ': r) a -> Sem r a runCdrStore = interpret $ \case FetchCdrs accountId -> gets (\m -> m M.! accountId) © Pawel Szulc, @EncodePanda, [email protected] 268
  199. data InvoiceStore m a where StoreInvoice :: AccountId -> Invoice

    -> InvoiceStore m () GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber type InvoiceMap = M.Map (AccountId, InvoiceNumber) Invoice runInvoiceStore :: Member (State InvoiceMap) r => Member (Embed IO) r => Sem (InvoiceStore ': r) a -> Sem r a runInvoiceStore = interpret $ \case StoreInvoice accountId invoice -> modify (M.insert (accountId, invoiceNumber invoice) invoice) GenNextInvoiceNumber accountId -> embed $ fmap (InvoiceNumber . toText) nextRandom © Pawel Szulc, @EncodePanda, [email protected] 269
  200. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 270
  201. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 271
  202. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 272
  203. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 273
  204. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 274
  205. profile :: Profile profile = Profile "John" "Smith" address plan

    where address = Address "Backer Street" "221b" "2" "London" "United Kingdom" plan = Plan 10 1 © Pawel Szulc, @EncodePanda, [email protected] 275
  206. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 276
  207. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 277
  208. cdrs :: AccountId -> [Cdr] cdrs accountId = [ cdr

    "8abbe08f-4b64-4263-b000-13f3ff77a0c6" Voice 10 , cdr "bed067b0-3e79-429d-8b96-d1f2c96e79ba" Sms 1 , cdr "d4bea3d9-a2a7-44cc-8a8d-301051860761" Voice 30 ] © Pawel Szulc, @EncodePanda, [email protected] 278
  209. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 279
  210. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 280
  211. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 281
  212. main :: IO () main = execute where accountId =

    AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM © Pawel Szulc, @EncodePanda, [email protected] 282
  213. main :: IO () main = execute >>= putStrLn.prettyPrint where

    accountId = AccountId 1000 execute = generateInvoice accountId & runCrm & runCdrStore & runInvoiceStore & evalState @CrmMap (M.singleton accountId profile) & evalState @CdrMap (M.singleton accountId (cdrs accountId)) & evalState @InvoiceMap M.empty & runM prettyPrint = unpack.toStrict.encodePretty © Pawel Szulc, @EncodePanda, [email protected] 283
  214. { "fullName": { "first": "John", "last": "Smith" }, "deliveryAddress": {

    "country": "United Kingdom", "num": "2", "street": "Backer Street", "house": "221b", "city": "London" }, "invoiceNumber": "136172ef-95cb-4714-924a-4d3f9c5e5fd6", "total": 401 } © Pawel Szulc, @EncodePanda, [email protected] 284