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

Maintainable Software Architecture in Haskell (with Polysemy)

  • 1.
    Maintainable Software Architeture inHaskell (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 isa 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 inHaskell © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 8
  • 9.
    Our plan fortoday 1. Coding Dojo / Hack day 2. Real world example • problem • approach • consequences • Polysemy © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 9
  • 10.
    Does writing codesucks? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 10
  • 11.
    Why writing codesucks (sometimes)? © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 11
  • 12.
    Coding Kata: Writea sorting algorithm © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 12
  • 13.
    "As a BillingSystem 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 theirnature 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 tomaintain function if it only manipulates data. © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 16
  • 17.
    -- | takean 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.
    -- | takean 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.
    -- | takean 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.
    -- | takean 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.
    -- | takean 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.
    -- | takean 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 totest 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.
    -- | takean 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 withsomething simple © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 25
  • 26.
    -- | takeInt, 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.
    -- | takeInt, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 29
  • 30.
    -- | takeInt, return +1 as text doStuff :: Int -> String doStuff i = "New value: " ++ (show $ i + 1) © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 30
  • 31.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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 :: IORefInMemStorage -> 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 :: IORefInMemStorage -> 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 :: IORefInMemStorage -> 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 :: IORefInMemStorage -> 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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 twoactions, passing any value produced by the first as an argument to the second." © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 130
  • 131.
    ??? :: ma -> (a -> m b) -> m b © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 131
  • 132.
    >>= :: ma -> (a -> m b) -> m b © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 132
  • 133.
    instance Monad Storagewhere (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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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.
    -- | takeInt, 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 :: (fx -> 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? © PawelSzulc, @EncodePanda, paul.szulc@gmail.com 169
  • 170.
    "As a BillingSystem 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 invoicefor 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.
  • 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 Consoler => 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 needis an interpreter © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 222
  • 223.
    data Console ma 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 ma 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 ma 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 ma 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 ma 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 ma 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 ma 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 ma 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 vm 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 vm 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 Crmm 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 CdrStorem 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 InvoiceStorem 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 InvoiceStorem a where GenNextInvoiceNumber :: AccountId -> InvoiceStore m InvoiceNumber makeSem ''InvoiceStore © Pawel Szulc, @EncodePanda, paul.szulc@gmail.com 261
  • 262.
    import Polysemy data InvoiceStorem 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 CdrStorer => 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 ma 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 ma 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 ma 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