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

Maintainable Software Architecture in Haskell

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.

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