SlideShare a Scribd company logo
1 of 285
Download to read offline
Maintainable Software Architeture
in Haskell (with Polysemy)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 1
maintain [ meyn-teyn ]
verb (used with object)
1. to keep in existence
2. to keep in an appropriate condition, operation, or force; keep
unimpaired:
3. to keep in a specified state, position, etc.
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 2
maintain [ meyn-teyn ]
verb (used with object)
1. to keep in existence
2. to keep in an appropriate condition, operation, or force; keep
unimpaired:
3. to keep in a specified state, position, etc.
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 3
maintain [ meyn-teyn ]
verb (used with object)
1. to keep in existence
2. to keep in an appropriate condition, operation, or force; keep
unimpaired:
3. to keep in a specified state, position, etc.
... in Haskell?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 4
"Socialism Haskell is a system
language which heroically
overcomes difficulties unknown in
any other system language"
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 5
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 6
Emphasis on what, less focus on
why?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 7
Maintainable Software Architeture
in Haskell
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 8
Our plan for today
1. Coding Dojo / Hack day
2. Real world example
• problem
• approach
• consequences
• Polysemy
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 9
Does writing code sucks?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 10
Why writing code sucks (sometimes)?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 11
Coding Kata: Write a sorting
algorithm
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 12
"As a Billing System user I want to
generate an invoice for a given
account based on its current
system use"
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 13
Functions and their nature
1. Manipulate data (f :: Input -> Output)
2. Interact with an outside world
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 14
doStuff :: Int -> Int
doStuff i = i + 1
Why this function is soooo good?
• easy to test
• you will be notified if its behavior changes
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 15
It's easy to maintain function if it
only manipulates data.
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 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, paul.szulc@gmail.com 17
-- | 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, paul.szulc@gmail.com 18
-- | 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, paul.szulc@gmail.com 19
-- | 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, paul.szulc@gmail.com 20
-- | 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, paul.szulc@gmail.com 21
-- | 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, paul.szulc@gmail.com 22
It's easy to test and maintain
function if it only manipulates data.
Can we change "interactions with
the outside world" into data?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 23
-- | 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, paul.szulc@gmail.com 24
Let's start with something simple
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 25
-- | take Int, return +1 as text
doStuff :: Int -> String
doStuff i = "New value: " ++ (show $ i + 1)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 26
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, paul.szulc@gmail.com 27
module Main where
main :: IO ()
main = putStrLn $ doStuff 10
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 28
-- | take Int, return +1 as text
doStuff :: Int -> String
doStuff i = "New value: " ++ (show $ i + 1)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 29
-- | take Int, return +1 as text
doStuff :: Int -> String
doStuff i = "New value: " ++ (show $ i + 1)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 30
-- | take Int, store it, return +1 as text
doStuff :: Int -> String
doStuff i = "New value: " ++ (show $ i + 1)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 31
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 32
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 33
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 34
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 35
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 36
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 37
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 38
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 39
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 40
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 41
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, paul.szulc@gmail.com 42
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, paul.szulc@gmail.com 43
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, paul.szulc@gmail.com 44
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, paul.szulc@gmail.com 45
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, paul.szulc@gmail.com 46
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, paul.szulc@gmail.com 47
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, paul.szulc@gmail.com 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, paul.szulc@gmail.com 49
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 50
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 51
doStuff :: UUID -> Int -> (Storage, String)
interpret :: (Storage, String) -> IO String
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 52
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, paul.szulc@gmail.com 53
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, paul.szulc@gmail.com 54
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, paul.szulc@gmail.com 55
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, paul.szulc@gmail.com 56
main :: IO ()
main = do
ioRef <- newIORef M.empty
uuid <- nextRandom
res <- interpret ioRef (doStuff uuid 10)
putStrLn res
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 57
-- | 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, paul.szulc@gmail.com 58
-- | 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, paul.szulc@gmail.com 59
-- | 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, paul.szulc@gmail.com 60
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 61
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 62
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 63
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 64
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, paul.szulc@gmail.com 65
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, paul.szulc@gmail.com 66
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, paul.szulc@gmail.com 67
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, paul.szulc@gmail.com 68
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, paul.szulc@gmail.com 69
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, paul.szulc@gmail.com 70
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, paul.szulc@gmail.com 71
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, paul.szulc@gmail.com 72
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, paul.szulc@gmail.com 73
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, paul.szulc@gmail.com 74
main :: IO ()
main = do
ioRef <- newIORef M.empty
uuid <- nextRandom
res <- interpret ioRef (doStuff uuid 10)
putStrLn res
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 75
-- | 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, paul.szulc@gmail.com 76
-- | 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, paul.szulc@gmail.com 77
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, paul.szulc@gmail.com 78
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, paul.szulc@gmail.com 79
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 80
doStuff :: UUID -> Int -> ([Storage], String)
interpret :: ([Storage], String) -> IO String
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 81
sthElse :: UUID -> Int -> ([Storage], Int)
interpret :: ([Storage], String) -> IO String
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 82
sthElse :: UUID -> Int -> ([Storage], Int)
interpret :: ([Storage], a) -> IO a
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 83
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, paul.szulc@gmail.com 84
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, paul.szulc@gmail.com 85
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, paul.szulc@gmail.com 86
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, paul.szulc@gmail.com 87
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, paul.szulc@gmail.com 88
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, paul.szulc@gmail.com 89
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, paul.szulc@gmail.com 90
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, paul.szulc@gmail.com 91
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, paul.szulc@gmail.com 92
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, paul.szulc@gmail.com 93
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, paul.szulc@gmail.com 94
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, paul.szulc@gmail.com 95
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, paul.szulc@gmail.com 96
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, paul.szulc@gmail.com 97
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, paul.szulc@gmail.com 98
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, paul.szulc@gmail.com 99
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, paul.szulc@gmail.com 100
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, paul.szulc@gmail.com 101
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, paul.szulc@gmail.com 102
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, paul.szulc@gmail.com 103
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, paul.szulc@gmail.com 104
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, paul.szulc@gmail.com 105
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, paul.szulc@gmail.com 106
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, paul.szulc@gmail.com 107
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 108
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 109
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, paul.szulc@gmail.com 110
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, paul.szulc@gmail.com 111
data Storage k =
Done k
| Persist UUID Int (Storage k)
deriving stock (Eq, Show)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 112
data Storage k =
Done k
| Persist UUID Int (Storage k)
deriving stock (Functor, Eq, Show)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 113
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, paul.szulc@gmail.com 114
-- | 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, paul.szulc@gmail.com 115
-- | 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, paul.szulc@gmail.com 116
-- | 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, paul.szulc@gmail.com 117
-- | 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, paul.szulc@gmail.com 118
-- | 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, paul.szulc@gmail.com 119
-- | 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, paul.szulc@gmail.com 120
-- | 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, paul.szulc@gmail.com 121
-- | 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, paul.szulc@gmail.com 122
-- | 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, paul.szulc@gmail.com 123
-- | 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, paul.szulc@gmail.com 124
-- | 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, paul.szulc@gmail.com 125
data Storage k =
Done k
| Persist UUID Int (Storage k)
deriving stock (Functor)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 126
data Storage k =
Done k
| Persist UUID Int (Storage k)
| Fetch UUID (Maybe Int -> Storage k)
deriving stock (Functor)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 127
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, paul.szulc@gmail.com 128
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, paul.szulc@gmail.com 129
"Sequentially compose two actions,
passing any value produced by the
first as an argument to the second."
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 130
??? :: m a -> (a -> m b) -> m b
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 131
>>= :: m a -> (a -> m b) -> m b
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 132
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, paul.szulc@gmail.com 133
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 134
-- | 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, paul.szulc@gmail.com 135
-- | 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, paul.szulc@gmail.com 136
-- | 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, paul.szulc@gmail.com 137
-- | 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, paul.szulc@gmail.com 138
-- | 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, paul.szulc@gmail.com 139
-- | 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, paul.szulc@gmail.com 140
-- | 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, paul.szulc@gmail.com 141
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, paul.szulc@gmail.com 142
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, paul.szulc@gmail.com 143
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, paul.szulc@gmail.com 144
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, paul.szulc@gmail.com 145
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, paul.szulc@gmail.com 146
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, paul.szulc@gmail.com 147
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, paul.szulc@gmail.com 148
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, paul.szulc@gmail.com 149
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, paul.szulc@gmail.com 150
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, paul.szulc@gmail.com 151
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 152
data Storage k =
Done k
| Persist UUID Int (Storage k)
| Fetch UUID (Maybe Int -> Storage k)
deriving stock (Functor)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 153
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, paul.szulc@gmail.com 154
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, paul.szulc@gmail.com 155
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, paul.szulc@gmail.com 156
data Storage k =
Persist UUID Int k
| Fetch UUID (Maybe Int -> k)
deriving stock (Functor)
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 157
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, paul.szulc@gmail.com 158
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, paul.szulc@gmail.com 159
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, paul.szulc@gmail.com 160
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, paul.szulc@gmail.com 161
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, paul.szulc@gmail.com 162
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, paul.szulc@gmail.com 163
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, paul.szulc@gmail.com 164
interpretFree :: (f x -> m x) -> Free f a -> m a
interpret :: IORef InMemStorage -> Storage a -> IO a
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 165
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, paul.szulc@gmail.com 166
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, paul.szulc@gmail.com 167
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, paul.szulc@gmail.com 168
Free Monads?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 169
"As a Billing System user I want to
generate an invoice for a given
account based on its current
system use"
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 170
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 171
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 172
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 173
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 174
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 175
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 176
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 177
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 178
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 179
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 180
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 181
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 182
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 183
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 184
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, paul.szulc@gmail.com 185
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 186
https://github.com/polysemy-research/polysemy
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 187
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 188
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 189
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 190
Sem r a___
program :: Sem '[Console, (Random Int)] Int
___
.________________________________________^
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 191
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 192
Sem r a________________________
program :: Sem '[Console, (Random Int)] Int
________________________
.________________________^
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 193
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 194
Sem r aprogram ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
.
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 195
Sem r aprogram ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
.________^
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 196
Sem r aprogram ::
Member Console r <|
=> Member (Random Int) r <|
=> Sem r Int |
.________^_____________________|
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 197
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, paul.szulc@gmail.com 198
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, paul.szulc@gmail.com 199
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, paul.szulc@gmail.com 200
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, paul.szulc@gmail.com 201
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, paul.szulc@gmail.com 202
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, paul.szulc@gmail.com 203
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, paul.szulc@gmail.com 204
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, paul.szulc@gmail.com 205
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, paul.szulc@gmail.com 206
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, paul.szulc@gmail.com 207
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, paul.szulc@gmail.com 208
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, paul.szulc@gmail.com 209
-- 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, paul.szulc@gmail.com 210
-- 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, paul.szulc@gmail.com 211
-- 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, paul.szulc@gmail.com 212
-- 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, paul.szulc@gmail.com 213
-- 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, paul.szulc@gmail.com 214
-- 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, paul.szulc@gmail.com 215
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, paul.szulc@gmail.com 216
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, paul.szulc@gmail.com 217
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, paul.szulc@gmail.com 218
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, paul.szulc@gmail.com 219
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, paul.szulc@gmail.com 220
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, paul.szulc@gmail.com 221
What we need is an interpreter
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 222
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, paul.szulc@gmail.com 223
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, paul.szulc@gmail.com 224
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, paul.szulc@gmail.com 225
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, paul.szulc@gmail.com 226
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, paul.szulc@gmail.com 227
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, paul.szulc@gmail.com 228
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, paul.szulc@gmail.com 229
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, paul.szulc@gmail.com 230
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, paul.szulc@gmail.com 231
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, paul.szulc@gmail.com 232
main :: IO ()
main = execute >>= putStrLn.show
where
execute = program
& runConsoleIO
& runRandomIO
& runM
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 233
main :: IO ()
main = execute >>= putStrLn.show
where
execute = program -- Sem '[Console, Random Int ] Int
& runConsoleIO
& runRandomIO
& runM
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 234
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, paul.szulc@gmail.com 235
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, paul.szulc@gmail.com 236
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, paul.szulc@gmail.com 237
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 238
data Invoice = Invoice
{ invoiceNumber :: InvoiceNumber
, fullName :: FullName
, deliveryAddress :: Address
, total :: Cent
}
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 239
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, paul.szulc@gmail.com 240
data Invoice = Invoice
{ invoiceNumber :: InvoiceNumber
, fullName :: FullName
, deliveryAddress :: Address
, total :: Cent
}
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 241
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, paul.szulc@gmail.com 242
data Plan = Plan
{ voiceCost :: Cent
, smsCost :: Cent
}
data Profile = Profile
{ firstName :: Text
, lastName :: Text
, address :: Address
, plan :: Plan
}
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 243
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, paul.szulc@gmail.com 244
mkInvoice ::
InvoiceNumber
-> Profile
-> [Cdr]
-> Invoice
mkInvoice = ...
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 245
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 246
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 247
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 248
import Polysemy
data Crm m a where
GetProfile :: AccountId -> Crm m Profile
makeSem ''Crm
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 249
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 250
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 251
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 252
import Polysemy
data CdrStore m a where
FetchCdrs :: AccountId -> CdrStore m [Cdr]
makeSem ''CdrStore
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 253
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 254
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 255
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 256
import Polysemy
data InvoiceStore m a where
GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber
makeSem ''InvoiceStore
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 257
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 258
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 259
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 260
import Polysemy
data InvoiceStore m a where
GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber
makeSem ''InvoiceStore
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 261
import Polysemy
data InvoiceStore m a where
GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber
StoreInvoice :: AccountId -> Invoice -> InvoiceStore m ()
makeSem ''InvoiceStore
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 262
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 263
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 264
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, paul.szulc@gmail.com 265
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, paul.szulc@gmail.com 266
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, paul.szulc@gmail.com 267
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, paul.szulc@gmail.com 268
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, paul.szulc@gmail.com 269
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, paul.szulc@gmail.com 270
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, paul.szulc@gmail.com 271
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, paul.szulc@gmail.com 272
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, paul.szulc@gmail.com 273
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, paul.szulc@gmail.com 274
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, paul.szulc@gmail.com 275
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, paul.szulc@gmail.com 276
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, paul.szulc@gmail.com 277
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, paul.szulc@gmail.com 278
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, paul.szulc@gmail.com 279
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, paul.szulc@gmail.com 280
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, paul.szulc@gmail.com 281
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, paul.szulc@gmail.com 282
{
"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, paul.szulc@gmail.com 283
Maintainable?
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 284
Thank You!Pawel Szulc
twitter: @EncodePanda
email: paul.szulc@gmail.com
© Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 285

More Related Content

What's hot

CEH - Module4 : Enumeration
CEH - Module4 : EnumerationCEH - Module4 : Enumeration
CEH - Module4 : EnumerationAvirot Mitamura
 
Versioned State Stores in Kafka Streams with Victoria Xia
Versioned State Stores in Kafka Streams with Victoria XiaVersioned State Stores in Kafka Streams with Victoria Xia
Versioned State Stores in Kafka Streams with Victoria XiaHostedbyConfluent
 
protocols of concurrency control
protocols of concurrency controlprotocols of concurrency control
protocols of concurrency controlMOHIT DADU
 
VerneMQ - Distributed MQTT Broker
VerneMQ - Distributed MQTT BrokerVerneMQ - Distributed MQTT Broker
VerneMQ - Distributed MQTT BrokerAdriano Pimpini
 
Kafka Streams for Java enthusiasts
Kafka Streams for Java enthusiastsKafka Streams for Java enthusiasts
Kafka Streams for Java enthusiastsSlim Baltagi
 
Consumer offset management in Kafka
Consumer offset management in KafkaConsumer offset management in Kafka
Consumer offset management in KafkaJoel Koshy
 
It's Time To Stop Using Lambda Architecture
It's Time To Stop Using Lambda ArchitectureIt's Time To Stop Using Lambda Architecture
It's Time To Stop Using Lambda ArchitectureYaroslav Tkachenko
 
System protection in Operating System
System protection in Operating SystemSystem protection in Operating System
System protection in Operating Systemsohaildanish
 
Real time operating systems (rtos) concepts 5
Real time operating systems (rtos) concepts 5Real time operating systems (rtos) concepts 5
Real time operating systems (rtos) concepts 5Abu Bakr Ramadan
 
Introduction to Apache Kafka
Introduction to Apache KafkaIntroduction to Apache Kafka
Introduction to Apache KafkaJeff Holoman
 
Kafka At Scale in the Cloud
Kafka At Scale in the CloudKafka At Scale in the Cloud
Kafka At Scale in the Cloudconfluent
 
Kafka Tutorial - DevOps, Admin and Ops
Kafka Tutorial - DevOps, Admin and OpsKafka Tutorial - DevOps, Admin and Ops
Kafka Tutorial - DevOps, Admin and OpsJean-Paul Azar
 

What's hot (20)

Compiler unit 2&3
Compiler unit 2&3Compiler unit 2&3
Compiler unit 2&3
 
CEH - Module4 : Enumeration
CEH - Module4 : EnumerationCEH - Module4 : Enumeration
CEH - Module4 : Enumeration
 
Versioned State Stores in Kafka Streams with Victoria Xia
Versioned State Stores in Kafka Streams with Victoria XiaVersioned State Stores in Kafka Streams with Victoria Xia
Versioned State Stores in Kafka Streams with Victoria Xia
 
protocols of concurrency control
protocols of concurrency controlprotocols of concurrency control
protocols of concurrency control
 
Concurrency control
Concurrency controlConcurrency control
Concurrency control
 
Semaphore
SemaphoreSemaphore
Semaphore
 
VerneMQ - Distributed MQTT Broker
VerneMQ - Distributed MQTT BrokerVerneMQ - Distributed MQTT Broker
VerneMQ - Distributed MQTT Broker
 
Kafka Streams for Java enthusiasts
Kafka Streams for Java enthusiastsKafka Streams for Java enthusiasts
Kafka Streams for Java enthusiasts
 
Callback Function
Callback FunctionCallback Function
Callback Function
 
Consumer offset management in Kafka
Consumer offset management in KafkaConsumer offset management in Kafka
Consumer offset management in Kafka
 
kafka
kafkakafka
kafka
 
Cs419 lec10 left recursion and left factoring
Cs419 lec10   left recursion and left factoringCs419 lec10   left recursion and left factoring
Cs419 lec10 left recursion and left factoring
 
Syntax analysis
Syntax analysisSyntax analysis
Syntax analysis
 
It's Time To Stop Using Lambda Architecture
It's Time To Stop Using Lambda ArchitectureIt's Time To Stop Using Lambda Architecture
It's Time To Stop Using Lambda Architecture
 
System protection in Operating System
System protection in Operating SystemSystem protection in Operating System
System protection in Operating System
 
Real time operating systems (rtos) concepts 5
Real time operating systems (rtos) concepts 5Real time operating systems (rtos) concepts 5
Real time operating systems (rtos) concepts 5
 
Introduction to Apache Kafka
Introduction to Apache KafkaIntroduction to Apache Kafka
Introduction to Apache Kafka
 
Kafka At Scale in the Cloud
Kafka At Scale in the CloudKafka At Scale in the Cloud
Kafka At Scale in the Cloud
 
Java Memory Management Tricks
Java Memory Management Tricks Java Memory Management Tricks
Java Memory Management Tricks
 
Kafka Tutorial - DevOps, Admin and Ops
Kafka Tutorial - DevOps, Admin and OpsKafka Tutorial - DevOps, Admin and Ops
Kafka Tutorial - DevOps, Admin and Ops
 

More from Pawel Szulc

Getting acquainted with Lens
Getting acquainted with LensGetting acquainted with Lens
Getting acquainted with LensPawel Szulc
 
Painless Haskell
Painless HaskellPainless Haskell
Painless HaskellPawel Szulc
 
Trip with monads
Trip with monadsTrip with monads
Trip with monadsPawel Szulc
 
Trip with monads
Trip with monadsTrip with monads
Trip with monadsPawel Szulc
 
Illogical engineers
Illogical engineersIllogical engineers
Illogical engineersPawel Szulc
 
RChain - Understanding Distributed Calculi
RChain - Understanding Distributed CalculiRChain - Understanding Distributed Calculi
RChain - Understanding Distributed CalculiPawel Szulc
 
Illogical engineers
Illogical engineersIllogical engineers
Illogical engineersPawel Szulc
 
Understanding distributed calculi in Haskell
Understanding distributed calculi in HaskellUnderstanding distributed calculi in Haskell
Understanding distributed calculi in HaskellPawel Szulc
 
Software engineering the genesis
Software engineering  the genesisSoftware engineering  the genesis
Software engineering the genesisPawel Szulc
 
Make your programs Free
Make your programs FreeMake your programs Free
Make your programs FreePawel Szulc
 
Going bananas with recursion schemes for fixed point data types
Going bananas with recursion schemes for fixed point data typesGoing bananas with recursion schemes for fixed point data types
Going bananas with recursion schemes for fixed point data typesPawel Szulc
 
“Going bananas with recursion schemes for fixed point data types”
“Going bananas with recursion schemes for fixed point data types”“Going bananas with recursion schemes for fixed point data types”
“Going bananas with recursion schemes for fixed point data types”Pawel Szulc
 
Writing your own RDD for fun and profit
Writing your own RDD for fun and profitWriting your own RDD for fun and profit
Writing your own RDD for fun and profitPawel Szulc
 
The cats toolbox a quick tour of some basic typeclasses
The cats toolbox  a quick tour of some basic typeclassesThe cats toolbox  a quick tour of some basic typeclasses
The cats toolbox a quick tour of some basic typeclassesPawel Szulc
 
Introduction to type classes
Introduction to type classesIntroduction to type classes
Introduction to type classesPawel Szulc
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenPawel Szulc
 
Apache spark workshop
Apache spark workshopApache spark workshop
Apache spark workshopPawel Szulc
 
Introduction to type classes in 30 min
Introduction to type classes in 30 minIntroduction to type classes in 30 min
Introduction to type classes in 30 minPawel Szulc
 
Real world gobbledygook
Real world gobbledygookReal world gobbledygook
Real world gobbledygookPawel Szulc
 

More from Pawel Szulc (20)

Getting acquainted with Lens
Getting acquainted with LensGetting acquainted with Lens
Getting acquainted with Lens
 
Impossibility
ImpossibilityImpossibility
Impossibility
 
Painless Haskell
Painless HaskellPainless Haskell
Painless Haskell
 
Trip with monads
Trip with monadsTrip with monads
Trip with monads
 
Trip with monads
Trip with monadsTrip with monads
Trip with monads
 
Illogical engineers
Illogical engineersIllogical engineers
Illogical engineers
 
RChain - Understanding Distributed Calculi
RChain - Understanding Distributed CalculiRChain - Understanding Distributed Calculi
RChain - Understanding Distributed Calculi
 
Illogical engineers
Illogical engineersIllogical engineers
Illogical engineers
 
Understanding distributed calculi in Haskell
Understanding distributed calculi in HaskellUnderstanding distributed calculi in Haskell
Understanding distributed calculi in Haskell
 
Software engineering the genesis
Software engineering  the genesisSoftware engineering  the genesis
Software engineering the genesis
 
Make your programs Free
Make your programs FreeMake your programs Free
Make your programs Free
 
Going bananas with recursion schemes for fixed point data types
Going bananas with recursion schemes for fixed point data typesGoing bananas with recursion schemes for fixed point data types
Going bananas with recursion schemes for fixed point data types
 
“Going bananas with recursion schemes for fixed point data types”
“Going bananas with recursion schemes for fixed point data types”“Going bananas with recursion schemes for fixed point data types”
“Going bananas with recursion schemes for fixed point data types”
 
Writing your own RDD for fun and profit
Writing your own RDD for fun and profitWriting your own RDD for fun and profit
Writing your own RDD for fun and profit
 
The cats toolbox a quick tour of some basic typeclasses
The cats toolbox  a quick tour of some basic typeclassesThe cats toolbox  a quick tour of some basic typeclasses
The cats toolbox a quick tour of some basic typeclasses
 
Introduction to type classes
Introduction to type classesIntroduction to type classes
Introduction to type classes
 
Functional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heavenFunctional Programming & Event Sourcing - a pair made in heaven
Functional Programming & Event Sourcing - a pair made in heaven
 
Apache spark workshop
Apache spark workshopApache spark workshop
Apache spark workshop
 
Introduction to type classes in 30 min
Introduction to type classes in 30 minIntroduction to type classes in 30 min
Introduction to type classes in 30 min
 
Real world gobbledygook
Real world gobbledygookReal world gobbledygook
Real world gobbledygook
 

Recently uploaded

Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoffsammart93
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfhans926745
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 

Recently uploaded (20)

Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 

Maintainable Software Architecture in Haskell (with Polysemy)

  • 1. Maintainable Software Architeture in Haskell (with Polysemy) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 1
  • 2. maintain [ meyn-teyn ] verb (used with object) 1. to keep in existence 2. to keep in an appropriate condition, operation, or force; keep unimpaired: 3. to keep in a specified state, position, etc. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 2
  • 3. maintain [ meyn-teyn ] verb (used with object) 1. to keep in existence 2. to keep in an appropriate condition, operation, or force; keep unimpaired: 3. to keep in a specified state, position, etc. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 3
  • 4. maintain [ meyn-teyn ] verb (used with object) 1. to keep in existence 2. to keep in an appropriate condition, operation, or force; keep unimpaired: 3. to keep in a specified state, position, etc. ... in Haskell? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 4
  • 5. "Socialism Haskell is a system language which heroically overcomes difficulties unknown in any other system language" © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 5
  • 6. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 6
  • 7. Emphasis on what, less focus on why? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 7
  • 8. Maintainable Software Architeture in Haskell © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 8
  • 9. Our plan for today 1. Coding Dojo / Hack day 2. Real world example • problem • approach • consequences • Polysemy © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 9
  • 10. Does writing code sucks? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 10
  • 11. Why writing code sucks (sometimes)? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 11
  • 12. Coding Kata: Write a sorting algorithm © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 12
  • 13. "As a Billing System user I want to generate an invoice for a given account based on its current system use" © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 13
  • 14. Functions and their nature 1. Manipulate data (f :: Input -> Output) 2. Interact with an outside world © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 14
  • 15. doStuff :: Int -> Int doStuff i = i + 1 Why this function is soooo good? • easy to test • you will be notified if its behavior changes © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 15
  • 16. It's easy to maintain function if it only manipulates data. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 16
  • 17. -- | 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, paul.szulc@gmail.com 17
  • 18. -- | 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, paul.szulc@gmail.com 18
  • 19. -- | 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, paul.szulc@gmail.com 19
  • 20. -- | 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, paul.szulc@gmail.com 20
  • 21. -- | 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, paul.szulc@gmail.com 21
  • 22. -- | 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, paul.szulc@gmail.com 22
  • 23. It's easy to test and maintain function if it only manipulates data. Can we change "interactions with the outside world" into data? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 23
  • 24. -- | 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, paul.szulc@gmail.com 24
  • 25. Let's start with something simple © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 25
  • 26. -- | take Int, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 26
  • 27. 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, paul.szulc@gmail.com 27
  • 28. module Main where main :: IO () main = putStrLn $ doStuff 10 © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 28
  • 29. -- | take Int, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 29
  • 30. -- | take Int, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 30
  • 31. -- | take Int, store it, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 31
  • 32. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 32
  • 33. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 33
  • 34. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 34
  • 35. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 35
  • 36. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 36
  • 37. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 37
  • 38. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 38
  • 39. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 39
  • 40. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 40
  • 41. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 41
  • 42. 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, paul.szulc@gmail.com 42
  • 43. 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, paul.szulc@gmail.com 43
  • 44. 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, paul.szulc@gmail.com 44
  • 45. 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, paul.szulc@gmail.com 45
  • 46. 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, paul.szulc@gmail.com 46
  • 47. 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, paul.szulc@gmail.com 47
  • 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, paul.szulc@gmail.com 48
  • 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, paul.szulc@gmail.com 49
  • 50. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 50
  • 51. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 51
  • 52. doStuff :: UUID -> Int -> (Storage, String) interpret :: (Storage, String) -> IO String © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 52
  • 53. 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, paul.szulc@gmail.com 53
  • 54. 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, paul.szulc@gmail.com 54
  • 55. 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, paul.szulc@gmail.com 55
  • 56. 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, paul.szulc@gmail.com 56
  • 57. main :: IO () main = do ioRef <- newIORef M.empty uuid <- nextRandom res <- interpret ioRef (doStuff uuid 10) putStrLn res © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 57
  • 58. -- | 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, paul.szulc@gmail.com 58
  • 59. -- | 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, paul.szulc@gmail.com 59
  • 60. -- | 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, paul.szulc@gmail.com 60
  • 61. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 61
  • 62. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 62
  • 63. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 63
  • 64. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 64
  • 65. 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, paul.szulc@gmail.com 65
  • 66. 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, paul.szulc@gmail.com 66
  • 67. 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, paul.szulc@gmail.com 67
  • 68. 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, paul.szulc@gmail.com 68
  • 69. 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, paul.szulc@gmail.com 69
  • 70. 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, paul.szulc@gmail.com 70
  • 71. 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, paul.szulc@gmail.com 71
  • 72. 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, paul.szulc@gmail.com 72
  • 73. 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, paul.szulc@gmail.com 73
  • 74. 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, paul.szulc@gmail.com 74
  • 75. main :: IO () main = do ioRef <- newIORef M.empty uuid <- nextRandom res <- interpret ioRef (doStuff uuid 10) putStrLn res © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 75
  • 76. -- | 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, paul.szulc@gmail.com 76
  • 77. -- | 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, paul.szulc@gmail.com 77
  • 78. 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, paul.szulc@gmail.com 78
  • 79. 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, paul.szulc@gmail.com 79
  • 80. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 80
  • 81. doStuff :: UUID -> Int -> ([Storage], String) interpret :: ([Storage], String) -> IO String © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 81
  • 82. sthElse :: UUID -> Int -> ([Storage], Int) interpret :: ([Storage], String) -> IO String © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 82
  • 83. sthElse :: UUID -> Int -> ([Storage], Int) interpret :: ([Storage], a) -> IO a © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 83
  • 84. 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, paul.szulc@gmail.com 84
  • 85. 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, paul.szulc@gmail.com 85
  • 86. 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, paul.szulc@gmail.com 86
  • 87. 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, paul.szulc@gmail.com 87
  • 88. 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, paul.szulc@gmail.com 88
  • 89. 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, paul.szulc@gmail.com 89
  • 90. 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, paul.szulc@gmail.com 90
  • 91. 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, paul.szulc@gmail.com 91
  • 92. 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, paul.szulc@gmail.com 92
  • 93. 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, paul.szulc@gmail.com 93
  • 94. 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, paul.szulc@gmail.com 94
  • 95. 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, paul.szulc@gmail.com 95
  • 96. 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, paul.szulc@gmail.com 96
  • 97. 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, paul.szulc@gmail.com 97
  • 98. 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, paul.szulc@gmail.com 98
  • 99. 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, paul.szulc@gmail.com 99
  • 100. 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, paul.szulc@gmail.com 100
  • 101. 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, paul.szulc@gmail.com 101
  • 102. 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, paul.szulc@gmail.com 102
  • 103. 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, paul.szulc@gmail.com 103
  • 104. 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, paul.szulc@gmail.com 104
  • 105. 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, paul.szulc@gmail.com 105
  • 106. 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, paul.szulc@gmail.com 106
  • 107. 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, paul.szulc@gmail.com 107
  • 108. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 108
  • 109. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 109
  • 110. 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, paul.szulc@gmail.com 110
  • 111. 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, paul.szulc@gmail.com 111
  • 112. data Storage k = Done k | Persist UUID Int (Storage k) deriving stock (Eq, Show) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 112
  • 113. data Storage k = Done k | Persist UUID Int (Storage k) deriving stock (Functor, Eq, Show) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 113
  • 114. 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, paul.szulc@gmail.com 114
  • 115. -- | 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, paul.szulc@gmail.com 115
  • 116. -- | 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, paul.szulc@gmail.com 116
  • 117. -- | 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, paul.szulc@gmail.com 117
  • 118. -- | 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, paul.szulc@gmail.com 118
  • 119. -- | 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, paul.szulc@gmail.com 119
  • 120. -- | 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, paul.szulc@gmail.com 120
  • 121. -- | 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, paul.szulc@gmail.com 121
  • 122. -- | 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, paul.szulc@gmail.com 122
  • 123. -- | 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, paul.szulc@gmail.com 123
  • 124. -- | 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, paul.szulc@gmail.com 124
  • 125. -- | 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, paul.szulc@gmail.com 125
  • 126. data Storage k = Done k | Persist UUID Int (Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 126
  • 127. data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 127
  • 128. 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, paul.szulc@gmail.com 128
  • 129. 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, paul.szulc@gmail.com 129
  • 130. "Sequentially compose two actions, passing any value produced by the first as an argument to the second." © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 130
  • 131. ??? :: m a -> (a -> m b) -> m b © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 131
  • 132. >>= :: m a -> (a -> m b) -> m b © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 132
  • 133. 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, paul.szulc@gmail.com 133
  • 134. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 134
  • 135. -- | 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, paul.szulc@gmail.com 135
  • 136. -- | 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, paul.szulc@gmail.com 136
  • 137. -- | 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, paul.szulc@gmail.com 137
  • 138. -- | 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, paul.szulc@gmail.com 138
  • 139. -- | 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, paul.szulc@gmail.com 139
  • 140. -- | 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, paul.szulc@gmail.com 140
  • 141. -- | 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, paul.szulc@gmail.com 141
  • 142. 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, paul.szulc@gmail.com 142
  • 143. 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, paul.szulc@gmail.com 143
  • 144. 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, paul.szulc@gmail.com 144
  • 145. 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, paul.szulc@gmail.com 145
  • 146. 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, paul.szulc@gmail.com 146
  • 147. 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, paul.szulc@gmail.com 147
  • 148. 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, paul.szulc@gmail.com 148
  • 149. 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, paul.szulc@gmail.com 149
  • 150. 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, paul.szulc@gmail.com 150
  • 151. 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, paul.szulc@gmail.com 151
  • 152. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 152
  • 153. data Storage k = Done k | Persist UUID Int (Storage k) | Fetch UUID (Maybe Int -> Storage k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 153
  • 154. 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, paul.szulc@gmail.com 154
  • 155. 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, paul.szulc@gmail.com 155
  • 156. 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, paul.szulc@gmail.com 156
  • 157. data Storage k = Persist UUID Int k | Fetch UUID (Maybe Int -> k) deriving stock (Functor) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 157
  • 158. 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, paul.szulc@gmail.com 158
  • 159. 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, paul.szulc@gmail.com 159
  • 160. 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, paul.szulc@gmail.com 160
  • 161. 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, paul.szulc@gmail.com 161
  • 162. 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, paul.szulc@gmail.com 162
  • 163. 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, paul.szulc@gmail.com 163
  • 164. 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, paul.szulc@gmail.com 164
  • 165. interpretFree :: (f x -> m x) -> Free f a -> m a interpret :: IORef InMemStorage -> Storage a -> IO a © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 165
  • 166. 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, paul.szulc@gmail.com 166
  • 167. 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, paul.szulc@gmail.com 167
  • 168. 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, paul.szulc@gmail.com 168
  • 169. Free Monads? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 169
  • 170. "As a Billing System user I want to generate an invoice for a given account based on its current system use" © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 170
  • 171. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 171
  • 172. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 172
  • 173. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 173
  • 174. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 174
  • 175. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 175
  • 176. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 176
  • 177. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 177
  • 178. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 178
  • 179. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 179
  • 180. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 180
  • 181. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 181
  • 182. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 182
  • 183. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 183
  • 184. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 184
  • 185. 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, paul.szulc@gmail.com 185
  • 186. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 186
  • 187. https://github.com/polysemy-research/polysemy © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 187
  • 188. Sem r a. program :: Sem '[Console, (Random Int)] Int © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 188
  • 189. Sem r a. program :: Sem '[Console, (Random Int)] Int © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 189
  • 190. Sem r a. program :: Sem '[Console, (Random Int)] Int © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 190
  • 191. Sem r a___ program :: Sem '[Console, (Random Int)] Int ___ .________________________________________^ © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 191
  • 192. Sem r a. program :: Sem '[Console, (Random Int)] Int © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 192
  • 193. Sem r a________________________ program :: Sem '[Console, (Random Int)] Int ________________________ .________________________^ © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 193
  • 194. Sem r a. program :: Sem '[Console, (Random Int)] Int © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 194
  • 195. Sem r aprogram :: Member Console r => Member (Random Int) r => Sem r Int . © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 195
  • 196. Sem r aprogram :: Member Console r => Member (Random Int) r => Sem r Int .________^ © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 196
  • 197. Sem r aprogram :: Member Console r <| => Member (Random Int) r <| => Sem r Int | .________^_____________________| © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 197
  • 198. 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, paul.szulc@gmail.com 198
  • 199. 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, paul.szulc@gmail.com 199
  • 200. 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, paul.szulc@gmail.com 200
  • 201. 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, paul.szulc@gmail.com 201
  • 202. 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, paul.szulc@gmail.com 202
  • 203. 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, paul.szulc@gmail.com 203
  • 204. 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, paul.szulc@gmail.com 204
  • 205. 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, paul.szulc@gmail.com 205
  • 206. 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, paul.szulc@gmail.com 206
  • 207. 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, paul.szulc@gmail.com 207
  • 208. 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, paul.szulc@gmail.com 208
  • 209. 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, paul.szulc@gmail.com 209
  • 210. -- 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, paul.szulc@gmail.com 210
  • 211. -- 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, paul.szulc@gmail.com 211
  • 212. -- 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, paul.szulc@gmail.com 212
  • 213. -- 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, paul.szulc@gmail.com 213
  • 214. -- 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, paul.szulc@gmail.com 214
  • 215. -- 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, paul.szulc@gmail.com 215
  • 216. 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, paul.szulc@gmail.com 216
  • 217. 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, paul.szulc@gmail.com 217
  • 218. 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, paul.szulc@gmail.com 218
  • 219. 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, paul.szulc@gmail.com 219
  • 220. 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, paul.szulc@gmail.com 220
  • 221. 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, paul.szulc@gmail.com 221
  • 222. What we need is an interpreter © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 222
  • 223. 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, paul.szulc@gmail.com 223
  • 224. 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, paul.szulc@gmail.com 224
  • 225. 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, paul.szulc@gmail.com 225
  • 226. 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, paul.szulc@gmail.com 226
  • 227. 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, paul.szulc@gmail.com 227
  • 228. 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, paul.szulc@gmail.com 228
  • 229. 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, paul.szulc@gmail.com 229
  • 230. 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, paul.szulc@gmail.com 230
  • 231. 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, paul.szulc@gmail.com 231
  • 232. 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, paul.szulc@gmail.com 232
  • 233. main :: IO () main = execute >>= putStrLn.show where execute = program & runConsoleIO & runRandomIO & runM © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 233
  • 234. main :: IO () main = execute >>= putStrLn.show where execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO & runRandomIO & runM © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 234
  • 235. 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, paul.szulc@gmail.com 235
  • 236. 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, paul.szulc@gmail.com 236
  • 237. 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, paul.szulc@gmail.com 237
  • 238. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 238
  • 239. data Invoice = Invoice { invoiceNumber :: InvoiceNumber , fullName :: FullName , deliveryAddress :: Address , total :: Cent } © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 239
  • 240. 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, paul.szulc@gmail.com 240
  • 241. data Invoice = Invoice { invoiceNumber :: InvoiceNumber , fullName :: FullName , deliveryAddress :: Address , total :: Cent } © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 241
  • 242. 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, paul.szulc@gmail.com 242
  • 243. data Plan = Plan { voiceCost :: Cent , smsCost :: Cent } data Profile = Profile { firstName :: Text , lastName :: Text , address :: Address , plan :: Plan } © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 243
  • 244. 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, paul.szulc@gmail.com 244
  • 245. mkInvoice :: InvoiceNumber -> Profile -> [Cdr] -> Invoice mkInvoice = ... © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 245
  • 246. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 246
  • 247. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 247
  • 248. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 248
  • 249. import Polysemy data Crm m a where GetProfile :: AccountId -> Crm m Profile makeSem ''Crm © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 249
  • 250. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 250
  • 251. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 251
  • 252. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 252
  • 253. import Polysemy data CdrStore m a where FetchCdrs :: AccountId -> CdrStore m [Cdr] makeSem ''CdrStore © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 253
  • 254. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 254
  • 255. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 255
  • 256. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 256
  • 257. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 257
  • 258. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 258
  • 259. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 259
  • 260. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 260
  • 261. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 261
  • 262. import Polysemy data InvoiceStore m a where GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber StoreInvoice :: AccountId -> Invoice -> InvoiceStore m () makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 262
  • 263. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 263
  • 264. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 264
  • 265. 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, paul.szulc@gmail.com 265
  • 266. 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, paul.szulc@gmail.com 266
  • 267. 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, paul.szulc@gmail.com 267
  • 268. 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, paul.szulc@gmail.com 268
  • 269. 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, paul.szulc@gmail.com 269
  • 270. 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, paul.szulc@gmail.com 270
  • 271. 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, paul.szulc@gmail.com 271
  • 272. 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, paul.szulc@gmail.com 272
  • 273. 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, paul.szulc@gmail.com 273
  • 274. 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, paul.szulc@gmail.com 274
  • 275. 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, paul.szulc@gmail.com 275
  • 276. 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, paul.szulc@gmail.com 276
  • 277. 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, paul.szulc@gmail.com 277
  • 278. 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, paul.szulc@gmail.com 278
  • 279. 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, paul.szulc@gmail.com 279
  • 280. 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, paul.szulc@gmail.com 280
  • 281. 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, paul.szulc@gmail.com 281
  • 282. 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, paul.szulc@gmail.com 282
  • 283. { "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, paul.szulc@gmail.com 283
  • 284. Maintainable? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 284
  • 285. Thank You!Pawel Szulc twitter: @EncodePanda email: paul.szulc@gmail.com © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 285