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

Threat hunting - Every day is hunting season
Threat hunting - Every day is hunting seasonThreat hunting - Every day is hunting season
Threat hunting - Every day is hunting seasonBen Boyd
 
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...North Texas Chapter of the ISSA
 
Smart materials
Smart materialsSmart materials
Smart materialsJobin Joy
 
Computer Security and Intrusion Detection(IDS/IPS)
Computer Security and Intrusion Detection(IDS/IPS)Computer Security and Intrusion Detection(IDS/IPS)
Computer Security and Intrusion Detection(IDS/IPS)LJ PROJECTS
 
Zero Trust Enterprise Network at Adobe
Zero Trust Enterprise Network at AdobeZero Trust Enterprise Network at Adobe
Zero Trust Enterprise Network at AdobeVishwas Manral
 

What's hot (7)

Threat hunting - Every day is hunting season
Threat hunting - Every day is hunting seasonThreat hunting - Every day is hunting season
Threat hunting - Every day is hunting season
 
Banana fiber reinforced composite material
Banana fiber reinforced composite materialBanana fiber reinforced composite material
Banana fiber reinforced composite material
 
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...
NTXISSACSC2 - Advanced Persistent Threat (APT) Life Cycle Management Monty Mc...
 
Smart materials
Smart materialsSmart materials
Smart materials
 
Computer Security and Intrusion Detection(IDS/IPS)
Computer Security and Intrusion Detection(IDS/IPS)Computer Security and Intrusion Detection(IDS/IPS)
Computer Security and Intrusion Detection(IDS/IPS)
 
Ceramic fibres
Ceramic fibresCeramic fibres
Ceramic fibres
 
Zero Trust Enterprise Network at Adobe
Zero Trust Enterprise Network at AdobeZero Trust Enterprise Network at Adobe
Zero Trust Enterprise Network at Adobe
 

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

Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024BookNet Canada
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDGMarianaLemus7
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfjimielynbastida
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxnull - The Open Security Community
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsPrecisely
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsAndrey Dotsenko
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 

Recently uploaded (20)

Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
New from BookNet Canada for 2024: BNC BiblioShare - Tech Forum 2024
 
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDG
 
Science&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdfScience&tech:THE INFORMATION AGE STS.pdf
Science&tech:THE INFORMATION AGE STS.pdf
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power Systems
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 

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