Painless Haskell
"This is a journey into sound.
A journey which along the way will bring to you magic ..."
sound [adjective]
- free from error, fallacy, or misapprehension
- exhibiting or based on thorough knowledge and experience
- logically valid and having true premises
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 1
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 2
Any sufficiently advanced
technology is indistinguishable
from magic.
— Arthur C. Clarke
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 3
The Roadmap
— aeson
— optparse-generic
— servant
— polysemy
!
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 4
Tools
— aeson
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 5
Tools
— aeson
A JSON parsing and encoding library optimized for ease of use
and high performance.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 6
How to get latest currency exchange rates?
https://api.exchangeratesapi.io/latest?base=EUR
{
"rates": {
"CHF": 1.0948,
"PLN": 4.3183,
"USD": 1.103,
"GBP": 0.90155
},
"base": "EUR",
"date": "2019-10-10"
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 7
{
"rates": {
"CHF": 1.0948,
"PLN": 4.3183,
"USD": 1.103,
"GBP": 0.90155
},
"base": "EUR",
"date": "2019-10-10"
}
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 8
{
"rates": {
"CHF": 1.0948,
"PLN": 4.3183,
"USD": 1.103,
"GBP": 0.90155
},
"base": "EUR",
"date": "2019-10-10"
}
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 9
Magic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 10
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 11
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 12
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 13
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 14
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 15
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 16
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
encodeExample :: IO ()
encodeExample = do
let rates = M.singleton "GBP" 1.12
let ratesResult = RatesResult rates "EUR" "2019-10-10"
let json = encode ratesResult
putStrLn json
λ> encodeExample
{"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 17
Science
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 18
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
instance ToJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 19
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
}
instance ToJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 20
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
instance ToJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 21
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
instance ToJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 22
rates.json
{
"base": "EUR",
"rates": {
"CHF": 1.0948,
"PLN": 4.3183,
"USD": 1.103,
"GBP": 0.90155
},
"date": "2019-10-10"
}
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 23
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 24
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 25
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 26
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 27
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 28
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 29
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
decodeExample :: IO ()
decodeExample = do
raw <- readFile "rates.json"
putStrLn $ extract $ decode raw
where
extract (Just (RatesResult _ base _)) = base
extract Nothing = "nothing"
λ> decodeExample
EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 30
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
instance ToJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 31
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data RatesResult = RatesResult {
rates :: M.Map String Double
, base :: String
, date :: String
} deriving (Generic, Show)
instance ToJSON RatesResult
instance FromJSON RatesResult
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 32
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 33
Tools
— aeson
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 34
Tools
— aeson
— optparse-generic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 35
Tools
— aeson
— optparse-generic
This library auto-generates command-line parsers for data
types using Haskell's built-in support for generic programming.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 36
What does it take to create a confernece?
You need to figure out the follwing:
— name
— number of attendees
— ticket price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 37
create :: String -> Int -> Double -> IO ()
create name capacity price =
putStrLn $ "Conference "
++ name
++ " for "
++ (show capacity)
++ " people created! Join now, just for "
++ (show price)
++ " EUR"
λ create "Lambda World" 650 150.0
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 38
create :: String -> Int -> Double -> IO ()
create name capacity price =
putStrLn $ "Conference "
++ name
++ " for "
++ (show capacity)
++ " people created! Join now, just for "
++ (show price)
++ " EUR"
λ create "Lambda World" 650 150.0
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 39
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 40
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 41
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 42
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 43
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
• Couldn't match type
Expected type: Int
Actual type: String
• In the second argument of ‘create’, namely ‘capacity’
|
30 | create name capacity price
| ^^^^^^^^
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 44
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 45
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name (read capacity) (read price)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 46
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name (read capacity) (read price)
$> creator "Lambda World" 650 150
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 47
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name (read capacity) (read price)
$> creator "Lambda World" 650
program: user error (Pattern match failure in do expression at src/Example.hs:29:3-25)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 48
main :: IO ()
main = do
[name, capacity, price] <- getArgs
create name (read capacity) (read price)
$> creator "Lambda World" 650
program: user error (Pattern match failure in do expression at src/Example.hs:29:3-25)
$> creator "Lambda World" 650.0 150
program: Prelude.read: no parse
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 49
Magic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 50
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
}
main :: IO ()
main = do
(Conference name capacity price) <- getRecord "Conference creator"
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 51
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
}
main :: IO ()
main = do
(Conference name capacity price) <- getRecord "Conference creator"
create name capacity price
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 52
$> creator --name "Lambda World" --capacity 650 --price 150
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
$> creator --name "Lambda World" --capacity 650
Missing: --price DOUBLE
Usage: program --name STRING --capacity INT --price DOUBLE
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 53
$> creator --name "Lambda World" --capacity 650 --price 150
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
$> creator --name "Lambda World" --capacity 650
Missing: --price DOUBLE
Usage: program --name STRING --capacity INT --price DOUBLE
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 54
$> creator --name "Lambda World" --capacity 650 --price 150
Conference Lambda World for 650 people created! Join now, just for 150.0 EUR
$> creator --name "Lambda World" --capacity 650
Missing: --price DOUBLE
Usage: program --name STRING --capacity INT --price DOUBLE
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 55
Science
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 56
{-# LANGUAGE DeriveGeneric #-}
import Options.Generic
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
}
instance ParseRecord Conference
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 57
{-# LANGUAGE DeriveGeneric #-}
import Options.Generic
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
}
instance ParseRecord Conference
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 58
{-# LANGUAGE DeriveGeneric #-}
import Options.Generic
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
} deriving (Generic, Show)
instance ParseRecord Conference
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 59
A bit more Magic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 60
data Conference = Conference {
name :: String
, capacity :: Int
, price :: Double
} deriving (Generic, Show)
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
--name STRING Name of the conference
--capacity INT Capacity of the venue
--price DOUBLE Price of the single ticket
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 61
data Conference = Conference {
name :: String <?> "Name of the conference"
, capacity :: Int <?> "Capacity of the venue"
, price :: Double <?> "Price of the single ticket"
} deriving (Generic, Show)
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
--name STRING Name of the conference
--capacity INT Capacity of the venue
--price DOUBLE Price of the single ticket
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 62
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 63
data Conference = Conference {
name :: String <?> "Name of the conference"
, capacity :: Int <?> "Capacity of the venue"
, price :: Double <?> "Price of the single ticket"
} deriving (Generic, Show)
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
--name STRING Name of the conference
--capacity INT Capacity of the venue
--price DOUBLE Price of the single ticket
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 64
data Conference = Conference {
name :: String <?> "Name of the conference"
, capacity :: Int <?> "Capacity of the venue"
, price :: Double <?> "Price of the single ticket"
} deriving (Generic, Show)
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
--name STRING Name of the conference
--capacity INT Capacity of the venue
--price DOUBLE Price of the single ticket
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 65
data Conference = Conference {
name :: String <?> "Name of the conference"
, capacity :: Int <?> "Capacity of the venue"
, price :: Double <?> "Price of the single ticket"
} deriving (Generic, Show)
$> creator --help
Conference creator
Usage: program --name STRING --capacity INT --price DOUBLE
Available options:
-h,--help Show this help text
--name STRING Name of the conference
--capacity INT Capacity of the venue
--price DOUBLE Price of the single ticket
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 66
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 67
Tools
— optparse-generic
— aeson
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 68
Tools
— optparse-generic
— aeson
— servant
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 69
Tools
— optparse-generic
— aeson
— servant
servant is a set of packages for declaring web APIs at the type-
level and then using those API specifications to: write servers,
obtain client functions, generate documentation and more...
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 70
So!ware Apocalypse 1
1 
The Coming Software Apocalypse
A small group of programmers wants to change how we code — before catastrophe strikes.
website.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 71
Plan B
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 72
data Color = White | Beige | Brown | Black | Silver
deriving (Generic, Show)
data Alpaca = Alpaca {
name :: String
, color :: Color
, age :: Int
} deriving (Generic, Show)
instance ToJSON Color
instance FromJSON Color
instance ToJSON Alpaca
instance FromJSON Alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 73
data Color = White | Beige | Brown | Black | Silver
deriving (Generic, Show)
data Alpaca = Alpaca {
name :: String
, color :: Color
, age :: Int
} deriving (Generic, Show)
instance ToJSON Color
instance FromJSON Color
instance ToJSON Alpaca
instance FromJSON Alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 74
data Color = White | Beige | Brown | Black | Silver
deriving (Generic, Show)
data Alpaca = Alpaca {
name :: String
, color :: Color
, age :: Int
} deriving (Generic, Show)
instance ToJSON Color
instance FromJSON Color
instance ToJSON Alpaca
instance FromJSON Alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 75
— /alpaca GET
Response: {"1":{"color":"White","age":0,"name":"Dotty"}}, 200
— /alpaca/1 GET
Response: {"color":"White","age":0,"name":"Dotty"}, 200
— /alpaca/1 PUT
Body: {"color":"White","age":0,"name":"Dotty"}
Response: NoContent, 201
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 76
Magic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 77
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 78
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
/alpaca GET
Response: {"1":{"color":"White","age":0,"name":"Dotty"}}, 200
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 79
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
/alpaca/1 GET
Response: {"color":"White","age":0,"name":"Dotty"}, 200
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 80
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
/alpaca/1 PUT
Body: {"color":"White","age":0,"name":"Dotty"}
Response: NoContent, 201
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 81
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 82
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
-- data Proxy a = Proxy
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 83
import Servant
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
-- data Proxy a = Proxy
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 84
Magic
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 85
Generating client functions
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 86
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 87
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 88
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 89
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 90
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 91
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
getAll :: m (Data.Map.Internal.Map Int Alpaca)
getAlpaca :: Int -> m Alpaca
putAlpaca :: Int -> Alpaca -> m NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 92
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 93
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
import Servant.Client
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 94
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
import Servant.Client
client alpacaApi
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 95
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
-- client functions
import Servant.Client
getAll :<|> getAlpaca :<|> putAlpaca = client alpacaApi
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 96
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 97
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 98
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 99
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 100
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 101
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 102
addNew :: Int -> String -> ClientM Alpaca
addNew id name = do
let alpaca = Alpaca name White 0
putAlpaca id alpaca
getAlpaca id
main :: IO ()
main = do
let baseUrl = (BaseUrl Http "localhost" 8080 "")
manager' <- newManager defaultManagerSettings
let clientEnv = mkClientEnv manager' baseUrl
res <- runClientM (addNew 1 "Reksio") clientEnv
case res of
Left err -> putStrLn $ "Error: " ++ show err
Right alpaca -> putStrLn $ show alpaca
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 103
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 104
Generating documentation
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 105
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
instance ToCapture (Capture 'alpacaId' Int) where
toCapture _ =
DocCapture "alpacaId"
"An id that uniquely identifies an alpaca in the system"
instance ToSample (Alpaca) where
toSamples _ = singleSample $ Alpaca "Dotty" White 0
instance ToSample (M.Map Int Alpaca) where
toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 106
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
instance ToCapture (Capture 'alpacaId' Int) where
toCapture _ =
DocCapture "alpacaId"
"An id that uniquely identifies an alpaca in the system"
instance ToSample (Alpaca) where
toSamples _ = singleSample $ Alpaca "Dotty" White 0
instance ToSample (M.Map Int Alpaca) where
toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 107
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
instance ToCapture (Capture 'alpacaId' Int) where
toCapture _ =
DocCapture "alpacaId"
"An id that uniquely identifies an alpaca in the system"
instance ToSample (Alpaca) where
toSamples _ = singleSample $ Alpaca "Dotty" White 0
instance ToSample (M.Map Int Alpaca) where
toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 108
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
instance ToCapture (Capture 'alpacaId' Int) where
toCapture _ =
DocCapture "alpacaId"
"An id that uniquely identifies an alpaca in the system"
instance ToSample (Alpaca) where
toSamples _ = singleSample $ Alpaca "Dotty" White 0
instance ToSample (M.Map Int Alpaca) where
toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 109
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
instance ToCapture (Capture 'alpacaId' Int) where
toCapture _ =
DocCapture "alpacaId"
"An id that uniquely identifies an alpaca in the system"
instance ToSample (Alpaca) where
toSamples _ = singleSample $ Alpaca "Dotty" White 0
instance ToSample (M.Map Int Alpaca) where
toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 110
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
import Servant.Docs
apiDocs :: API
apiDocs = docs alpacaApi
main :: IO ()
main = (writeFile "docs.md" . markdown) apiDocs
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 111
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
import Servant.Docs
apiDocs :: API
apiDocs = docs alpacaApi
main :: IO ()
main = (writeFile "docs.md" . markdown) apiDocs
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 112
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
alpacaApi :: Proxy AlpacaAPI
alpacaApi = Proxy
import Servant.Docs
apiDocs :: API
apiDocs = docs alpacaApi
main :: IO ()
main = (writeFile "docs.md" . markdown) apiDocs
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 113
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 114
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 115
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 116
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 117
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 118
Actually writing the server
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 119
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 120
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 121
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 122
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 123
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 124
type AlpacaAPI =
"alapaca" :> Get '[JSON] (M.Map Int Alpaca)
:<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca
:<|> "alapaca" :> Capture "alpacaId" Int
:> ReqBody '[JSON] Alpaca
:> PutCreated '[JSON] NoContent
fetchAll :: Monad m => m (M.Map Int Alpaca)
fetchAll = pure $ M.singleton 1 dummy
fetch :: Monad m => Int -> m Alpaca
fetch id = pure dummy
insert :: Monad m => Int -> Alpaca -> m NoContent
insert id alpaca = pure NoContent
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 125
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
:<|> insert
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 126
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
:<|> insert
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 127
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
--:<|> insert
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 128
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
--:<|> insert
Example.hs:29:8: error:
(...)
Expected type: Server AlpacaAPI
Actual type: Handler (M.Map Int Alpaca) :<|> (Int -> m0 Alpaca)
(...)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 129
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
:<|> insert
app :: Application
app = serve alpacaApi server
main :: IO ()
main = run 8080 app
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 130
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
:<|> insert
app :: Application
app = serve alpacaApi server
main :: IO ()
main = run 8080 app
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 131
server :: Server AlpacaAPI
server =
fetchAll
:<|> fetch
:<|> insert
app :: Application
app = serve alpacaApi server
main :: IO ()
main = run 8080 app
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 132
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 133
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 134
Tools
— aeson
— optparse-generic
— servant
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 135
Tools
— aeson
— optparse-generic
— servant
— polysemy
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 136
Tools
— aeson
— optparse-generic
— servant
— polysemy
!
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 137
Tools
— aeson
— optparse-generic
— servant
— polysemy
polysemy is a library for writing high-power, low-boilerplate,
zero-cost, domain specific languages. It allows you to separate
your business logic from your implementation details.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 138
Tools
— aeson
— optparse-generic
— servant
— polysemy
And in doing so, polysemy lets you turn your implementation
code into reusable library code.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 139
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 140
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 141
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 142
Sem r a___
program :: Sem '[Console, (Random Int)] Int
___
.________________________________________^
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 143
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 144
Sem r a________________________
program :: Sem '[Console, (Random Int)] Int
________________________
.________________________^
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 145
Sem r a.
program :: Sem '[Console, (Random Int)] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 146
Sem r aprogram ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
.
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 147
Sem r aprogram ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
.________^
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 148
Sem r aprogram ::
Member Console r <|
=> Member (Random Int) r <|
=> Sem r Int |
.________^_____________________|
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 149
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
makeSem ''Console
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 150
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
makeSem ''Console
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 151
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
makeSem ''Console
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 152
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
makeSem ''Console
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 153
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
makeSem ''Console
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 154
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, @rabbitonweb, paul.szulc@gmail.com 155
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, @rabbitonweb, paul.szulc@gmail.com 156
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, @rabbitonweb, paul.szulc@gmail.com 157
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
data Random v m a where
NextRandom :: Random v m v
makeSem ''Random
nextRandom :: Member (Random v) r => Sem r v
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 158
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, @rabbitonweb, paul.szulc@gmail.com 159
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, @rabbitonweb, paul.szulc@gmail.com 160
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, @rabbitonweb, paul.szulc@gmail.com 161
-- cheatsheet
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
nextRandom :: Member (Random v) r => Sem r v
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
program = do
printLine "Insert your number:"
i1 <- readLine
i2 <- nextRandom
pure (read i1 + i2)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 162
-- cheatsheet
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
nextRandom :: Member (Random v) r => Sem r v
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
program = do
printLine "Insert your number:"
i1 <- readLine
i2 <- nextRandom
pure (read i1 + i2)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 163
-- cheatsheet
printLine :: Member Console r => String -> Sem r ()
readLine :: Member Console r => Sem r String
nextRandom :: Member (Random v) r => Sem r v
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
program = do
printLine "Insert your number:"
i1 <- readLine
i2 <- nextRandom
pure (read i1 + i2)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 164
-- 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, @rabbitonweb, paul.szulc@gmail.com 165
-- 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, @rabbitonweb, paul.szulc@gmail.com 166
-- 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, @rabbitonweb, paul.szulc@gmail.com 167
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, @rabbitonweb, paul.szulc@gmail.com 168
run :: Sem '[] a -> a
runM :: Monad m
=> Sem '[Embed m] a -> m a
-- Sem '[Embed IO] a -> IO a
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
-- Sem '[Console, Random Int] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 169
run :: Sem '[] a -> a
runM :: Monad m
=> Sem '[Embed m] a -> m a
-- Sem '[Embed IO] a -> IO a
program ::
Member Console r
=> Member (Random Int) r
=> Sem r Int
-- Sem '[Console, Random Int] Int
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 170
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, @rabbitonweb, paul.szulc@gmail.com 171
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, @rabbitonweb, paul.szulc@gmail.com 172
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, @rabbitonweb, paul.szulc@gmail.com 173
What we need is an interpreter
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 174
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
runConsoleIO ::
Member (Embed IO) r
=> Sem (Console ': r) a -> Sem r a
runConsoleIO = interpret $ case
PrintLine line -> putStrLn line
ReadLine -> getLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 175
data Console m a where
PrintLine :: String -> Console m ()
ReadLine :: Console m String
runConsoleIO ::
Member (Embed IO) r
=> Sem (Console ': r) a -> Sem r a
runConsoleIO = interpret $ case
PrintLine line -> putStrLn line
ReadLine -> getLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 176
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, @rabbitonweb, paul.szulc@gmail.com 177
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, @rabbitonweb, paul.szulc@gmail.com 178
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, @rabbitonweb, paul.szulc@gmail.com 179
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, @rabbitonweb, paul.szulc@gmail.com 180
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, @rabbitonweb, paul.szulc@gmail.com 181
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, @rabbitonweb, paul.szulc@gmail.com 182
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, @rabbitonweb, paul.szulc@gmail.com 183
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, @rabbitonweb, paul.szulc@gmail.com 184
main :: IO ()
main = execute >>= putStrLn.show
where
execute = program
& runConsoleIO
& runRandomIO
& runM
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 185
main :: IO ()
main = execute >>= putStrLn.show
where
execute = program -- Sem '[Console, Random Int ] Int
& runConsoleIO
& runRandomIO
& runM
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 186
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, @rabbitonweb, paul.szulc@gmail.com 187
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, @rabbitonweb, paul.szulc@gmail.com 188
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, @rabbitonweb, paul.szulc@gmail.com 189
Can we write unit tests for this program?
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, @rabbitonweb, paul.szulc@gmail.com 190
Can we write unit tests for this program?
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, @rabbitonweb, paul.szulc@gmail.com 191
Can we write unit tests for this program?
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, @rabbitonweb, paul.szulc@gmail.com 192
Can we write unit tests for this program?
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, @rabbitonweb, paul.szulc@gmail.com 193
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 194
Can we write unit tests for this program?
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, @rabbitonweb, paul.szulc@gmail.com 195
runRandomConst :: Int -> Sem (Random Int ': r) a -> Sem r a
runRandomConst v = interpret $ case
NextRandom -> pure v
runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a
runConsoleConst constLine = interpret $ case
PrintLine line -> pure ()
ReadLine -> pure constLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 196
runRandomConst :: Int -> Sem (Random Int ': r) a -> Sem r a
runRandomConst v = interpret $ case
NextRandom -> pure v
runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a
runConsoleConst constLine = interpret $ case
PrintLine line -> pure ()
ReadLine -> pure constLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 197
runRandomConst :: Int -> Sem (Random Int ': r) a -> Sem r a
runRandomConst v = interpret $ case
NextRandom -> pure v
runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a
runConsoleConst constLine = interpret $ case
PrintLine line -> pure ()
ReadLine -> pure constLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 198
runRandomConst :: Int -> Sem (Random Int ': r) a -> Sem r a
runRandomConst v = interpret $ case
NextRandom -> pure v
runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a
runConsoleConst constLine = interpret $ case
PrintLine line -> pure ()
ReadLine -> pure constLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 199
runRandomConst :: Int -> Sem (Random Int ': r) a -> Sem r a
runRandomConst v = interpret $ case
NextRandom -> pure v
runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a
runConsoleConst constLine = interpret $ case
PrintLine line -> pure ()
ReadLine -> pure constLine
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 200
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program
& runConsoleConst "10"
& runRandomConst 20
& run
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 201
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program
& runConsoleConst "10"
& runRandomConst 20
& run
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 202
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program -- Sem '[Console, Random Int] Int
& runConsoleConst "10"
& runRandomConst 20
& run
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 203
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program -- Sem '[Console, Random Int] Int
& runConsoleConst "10"-- Sem '[ Random Int] Int
& runRandomConst 20
& run
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 204
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program -- Sem '[Console, Random Int] Int
& runConsoleConst "10"-- Sem '[ Random Int] Int
& runRandomConst 20 -- Sem '[ ] Int
& run
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 205
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program -- Sem '[Console, Random Int] Int
& runConsoleConst "10"-- Sem '[ Random Int] Int
& runRandomConst 20 -- Sem '[ ] Int
& run -- Int
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 206
spec :: Spec
spec =
describe "program" $ do
it "can add two numbers together (random and from console)" $ do
let result = program -- Sem '[Console, Random Int] Int
& runConsoleConst "10"-- Sem '[ Random Int] Int
& runRandomConst 20 -- Sem '[ ] Int
& run -- Int
result `shouldBe` 30
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 207
Typelevel Programming
Magic vs Science
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 208
Typelevel Programming: Magic vs Science
— https://leanpub.com/thinking-with-types
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 209
Typelevel Programming: Magic vs Science
— https://leanpub.com/thinking-with-types
— https://www.parsonsmatt.org/2017/04/26/
basic_type_level_programming_in_haskell.html
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 210
Typelevel Programming: Magic vs Science
— https://leanpub.com/thinking-with-types
— https://www.parsonsmatt.org/2017/04/26/
basic_type_level_programming_in_haskell.html
— https://github.com/tdietert/types-as-specifications
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 211
What was the point of all this?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 212
Homework: Write an application
that combines aeson, optparse-
generic, servant and polysemy
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 213
Polysemy - where can I learn
more?
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 214
Polysemy - where can I learn more?
— Lets hack your homework together
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 215
Polysemy - where can I learn more?
— Lets hack your homework together
— Prerequisites: hangouts, craft beer, haskell
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 216
Polysemy - where can I learn more?
— Lets hack your homework together
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 217
Polysemy - where can I learn more?
— Lets hack your homework together
— https://github.com/rabbitonweb/todo-rest
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 218
Polysemy - where can I learn more?
— Lets hack your homework together
— https://github.com/rabbitonweb/todo-rest
— https://github.com/frost-org/frost
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 219
Polysemy: conquer complexity
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 220
Polysemy vs MTL/Tagless vs ...
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 221
And The Sopranos is the show I recommend
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 222
Because you never really know how it's gonna
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 223
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 224
Next year? :)
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 225
Pawel Szulc
twi!er: @rabbitonweb
email: paul.szulc@gmail.com
blog: https://rabbitonweb.com
© Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 226

Painless Haskell

  • 1.
    Painless Haskell "This isa journey into sound. A journey which along the way will bring to you magic ..." sound [adjective] - free from error, fallacy, or misapprehension - exhibiting or based on thorough knowledge and experience - logically valid and having true premises © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 1
  • 2.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 2
  • 3.
    Any sufficiently advanced technologyis indistinguishable from magic. — Arthur C. Clarke © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 3
  • 4.
    The Roadmap — aeson —optparse-generic — servant — polysemy ! © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 4
  • 5.
    Tools — aeson © PawelSzulc, @rabbitonweb, paul.szulc@gmail.com 5
  • 6.
    Tools — aeson A JSONparsing and encoding library optimized for ease of use and high performance. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 6
  • 7.
    How to getlatest currency exchange rates? https://api.exchangeratesapi.io/latest?base=EUR { "rates": { "CHF": 1.0948, "PLN": 4.3183, "USD": 1.103, "GBP": 0.90155 }, "base": "EUR", "date": "2019-10-10" } © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 7
  • 8.
    { "rates": { "CHF": 1.0948, "PLN":4.3183, "USD": 1.103, "GBP": 0.90155 }, "base": "EUR", "date": "2019-10-10" } data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 8
  • 9.
    { "rates": { "CHF": 1.0948, "PLN":4.3183, "USD": 1.103, "GBP": 0.90155 }, "base": "EUR", "date": "2019-10-10" } data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 9
  • 10.
    Magic © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 10
  • 11.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 11
  • 12.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 12
  • 13.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 13
  • 14.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 14
  • 15.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 15
  • 16.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 16
  • 17.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } encodeExample :: IO () encodeExample = do let rates = M.singleton "GBP" 1.12 let ratesResult = RatesResult rates "EUR" "2019-10-10" let json = encode ratesResult putStrLn json λ> encodeExample {"base":"EUR","rates":{"GBP":1.12},"date":"2019-10-10"} © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 17
  • 18.
    Science © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 18
  • 19.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } instance ToJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 19
  • 20.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } instance ToJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 20
  • 21.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) instance ToJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 21
  • 22.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) instance ToJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 22
  • 23.
    rates.json { "base": "EUR", "rates": { "CHF":1.0948, "PLN": 4.3183, "USD": 1.103, "GBP": 0.90155 }, "date": "2019-10-10" } © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 23
  • 24.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 24
  • 25.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 25
  • 26.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 26
  • 27.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 27
  • 28.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 28
  • 29.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 29
  • 30.
    data RatesResult =RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) decodeExample :: IO () decodeExample = do raw <- readFile "rates.json" putStrLn $ extract $ decode raw where extract (Just (RatesResult _ base _)) = base extract Nothing = "nothing" λ> decodeExample EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 30
  • 31.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) instance ToJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 31
  • 32.
    {-# LANGUAGE DeriveGeneric#-} import Data.Aeson import GHC.Generics data RatesResult = RatesResult { rates :: M.Map String Double , base :: String , date :: String } deriving (Generic, Show) instance ToJSON RatesResult instance FromJSON RatesResult © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 32
  • 33.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 33
  • 34.
    Tools — aeson © PawelSzulc, @rabbitonweb, paul.szulc@gmail.com 34
  • 35.
    Tools — aeson — optparse-generic ©Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 35
  • 36.
    Tools — aeson — optparse-generic Thislibrary auto-generates command-line parsers for data types using Haskell's built-in support for generic programming. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 36
  • 37.
    What does ittake to create a confernece? You need to figure out the follwing: — name — number of attendees — ticket price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 37
  • 38.
    create :: String-> Int -> Double -> IO () create name capacity price = putStrLn $ "Conference " ++ name ++ " for " ++ (show capacity) ++ " people created! Join now, just for " ++ (show price) ++ " EUR" λ create "Lambda World" 650 150.0 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 38
  • 39.
    create :: String-> Int -> Double -> IO () create name capacity price = putStrLn $ "Conference " ++ name ++ " for " ++ (show capacity) ++ " people created! Join now, just for " ++ (show price) ++ " EUR" λ create "Lambda World" 650 150.0 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 39
  • 40.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 40
  • 41.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 41
  • 42.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 42
  • 43.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 43
  • 44.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price • Couldn't match type Expected type: Int Actual type: String • In the second argument of ‘create’, namely ‘capacity’ | 30 | create name capacity price | ^^^^^^^^ © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 44
  • 45.
    main :: IO() main = do [name, capacity, price] <- getArgs create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 45
  • 46.
    main :: IO() main = do [name, capacity, price] <- getArgs create name (read capacity) (read price) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 46
  • 47.
    main :: IO() main = do [name, capacity, price] <- getArgs create name (read capacity) (read price) $> creator "Lambda World" 650 150 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 47
  • 48.
    main :: IO() main = do [name, capacity, price] <- getArgs create name (read capacity) (read price) $> creator "Lambda World" 650 program: user error (Pattern match failure in do expression at src/Example.hs:29:3-25) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 48
  • 49.
    main :: IO() main = do [name, capacity, price] <- getArgs create name (read capacity) (read price) $> creator "Lambda World" 650 program: user error (Pattern match failure in do expression at src/Example.hs:29:3-25) $> creator "Lambda World" 650.0 150 program: Prelude.read: no parse © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 49
  • 50.
    Magic © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 50
  • 51.
    data Conference =Conference { name :: String , capacity :: Int , price :: Double } main :: IO () main = do (Conference name capacity price) <- getRecord "Conference creator" create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 51
  • 52.
    data Conference =Conference { name :: String , capacity :: Int , price :: Double } main :: IO () main = do (Conference name capacity price) <- getRecord "Conference creator" create name capacity price © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 52
  • 53.
    $> creator --name"Lambda World" --capacity 650 --price 150 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR $> creator --name "Lambda World" --capacity 650 Missing: --price DOUBLE Usage: program --name STRING --capacity INT --price DOUBLE $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 53
  • 54.
    $> creator --name"Lambda World" --capacity 650 --price 150 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR $> creator --name "Lambda World" --capacity 650 Missing: --price DOUBLE Usage: program --name STRING --capacity INT --price DOUBLE $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 54
  • 55.
    $> creator --name"Lambda World" --capacity 650 --price 150 Conference Lambda World for 650 people created! Join now, just for 150.0 EUR $> creator --name "Lambda World" --capacity 650 Missing: --price DOUBLE Usage: program --name STRING --capacity INT --price DOUBLE $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 55
  • 56.
    Science © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 56
  • 57.
    {-# LANGUAGE DeriveGeneric#-} import Options.Generic data Conference = Conference { name :: String , capacity :: Int , price :: Double } instance ParseRecord Conference © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 57
  • 58.
    {-# LANGUAGE DeriveGeneric#-} import Options.Generic data Conference = Conference { name :: String , capacity :: Int , price :: Double } instance ParseRecord Conference © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 58
  • 59.
    {-# LANGUAGE DeriveGeneric#-} import Options.Generic data Conference = Conference { name :: String , capacity :: Int , price :: Double } deriving (Generic, Show) instance ParseRecord Conference © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 59
  • 60.
    A bit moreMagic © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 60
  • 61.
    data Conference =Conference { name :: String , capacity :: Int , price :: Double } deriving (Generic, Show) $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text --name STRING Name of the conference --capacity INT Capacity of the venue --price DOUBLE Price of the single ticket © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 61
  • 62.
    data Conference =Conference { name :: String <?> "Name of the conference" , capacity :: Int <?> "Capacity of the venue" , price :: Double <?> "Price of the single ticket" } deriving (Generic, Show) $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text --name STRING Name of the conference --capacity INT Capacity of the venue --price DOUBLE Price of the single ticket © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 62
  • 63.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 63
  • 64.
    data Conference =Conference { name :: String <?> "Name of the conference" , capacity :: Int <?> "Capacity of the venue" , price :: Double <?> "Price of the single ticket" } deriving (Generic, Show) $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text --name STRING Name of the conference --capacity INT Capacity of the venue --price DOUBLE Price of the single ticket © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 64
  • 65.
    data Conference =Conference { name :: String <?> "Name of the conference" , capacity :: Int <?> "Capacity of the venue" , price :: Double <?> "Price of the single ticket" } deriving (Generic, Show) $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text --name STRING Name of the conference --capacity INT Capacity of the venue --price DOUBLE Price of the single ticket © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 65
  • 66.
    data Conference =Conference { name :: String <?> "Name of the conference" , capacity :: Int <?> "Capacity of the venue" , price :: Double <?> "Price of the single ticket" } deriving (Generic, Show) $> creator --help Conference creator Usage: program --name STRING --capacity INT --price DOUBLE Available options: -h,--help Show this help text --name STRING Name of the conference --capacity INT Capacity of the venue --price DOUBLE Price of the single ticket © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 66
  • 67.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 67
  • 68.
    Tools — optparse-generic — aeson ©Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 68
  • 69.
    Tools — optparse-generic — aeson —servant © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 69
  • 70.
    Tools — optparse-generic — aeson —servant servant is a set of packages for declaring web APIs at the type- level and then using those API specifications to: write servers, obtain client functions, generate documentation and more... © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 70
  • 71.
    So!ware Apocalypse 1 1  TheComing Software Apocalypse A small group of programmers wants to change how we code — before catastrophe strikes. website. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 71
  • 72.
    Plan B © PawelSzulc, @rabbitonweb, paul.szulc@gmail.com 72
  • 73.
    data Color =White | Beige | Brown | Black | Silver deriving (Generic, Show) data Alpaca = Alpaca { name :: String , color :: Color , age :: Int } deriving (Generic, Show) instance ToJSON Color instance FromJSON Color instance ToJSON Alpaca instance FromJSON Alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 73
  • 74.
    data Color =White | Beige | Brown | Black | Silver deriving (Generic, Show) data Alpaca = Alpaca { name :: String , color :: Color , age :: Int } deriving (Generic, Show) instance ToJSON Color instance FromJSON Color instance ToJSON Alpaca instance FromJSON Alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 74
  • 75.
    data Color =White | Beige | Brown | Black | Silver deriving (Generic, Show) data Alpaca = Alpaca { name :: String , color :: Color , age :: Int } deriving (Generic, Show) instance ToJSON Color instance FromJSON Color instance ToJSON Alpaca instance FromJSON Alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 75
  • 76.
    — /alpaca GET Response:{"1":{"color":"White","age":0,"name":"Dotty"}}, 200 — /alpaca/1 GET Response: {"color":"White","age":0,"name":"Dotty"}, 200 — /alpaca/1 PUT Body: {"color":"White","age":0,"name":"Dotty"} Response: NoContent, 201 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 76
  • 77.
    Magic © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 77
  • 78.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 78
  • 79.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent /alpaca GET Response: {"1":{"color":"White","age":0,"name":"Dotty"}}, 200 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 79
  • 80.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent /alpaca/1 GET Response: {"color":"White","age":0,"name":"Dotty"}, 200 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 80
  • 81.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent /alpaca/1 PUT Body: {"color":"White","age":0,"name":"Dotty"} Response: NoContent, 201 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 81
  • 82.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 82
  • 83.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent -- data Proxy a = Proxy alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 83
  • 84.
    import Servant type AlpacaAPI= "alapaca" :> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent -- data Proxy a = Proxy alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 84
  • 85.
    Magic © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 85
  • 86.
    Generating client functions ©Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 86
  • 87.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 87
  • 88.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 88
  • 89.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 89
  • 90.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 90
  • 91.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 91
  • 92.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions getAll :: m (Data.Map.Internal.Map Int Alpaca) getAlpaca :: Int -> m Alpaca putAlpaca :: Int -> Alpaca -> m NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 92
  • 93.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 93
  • 94.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions import Servant.Client © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 94
  • 95.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions import Servant.Client client alpacaApi © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 95
  • 96.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy -- client functions import Servant.Client getAll :<|> getAlpaca :<|> putAlpaca = client alpacaApi © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 96
  • 97.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 97
  • 98.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 98
  • 99.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 99
  • 100.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 100
  • 101.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 101
  • 102.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 102
  • 103.
    addNew :: Int-> String -> ClientM Alpaca addNew id name = do let alpaca = Alpaca name White 0 putAlpaca id alpaca getAlpaca id main :: IO () main = do let baseUrl = (BaseUrl Http "localhost" 8080 "") manager' <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager' baseUrl res <- runClientM (addNew 1 "Reksio") clientEnv case res of Left err -> putStrLn $ "Error: " ++ show err Right alpaca -> putStrLn $ show alpaca © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 103
  • 104.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 104
  • 105.
    Generating documentation © PawelSzulc, @rabbitonweb, paul.szulc@gmail.com 105
  • 106.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent instance ToCapture (Capture 'alpacaId' Int) where toCapture _ = DocCapture "alpacaId" "An id that uniquely identifies an alpaca in the system" instance ToSample (Alpaca) where toSamples _ = singleSample $ Alpaca "Dotty" White 0 instance ToSample (M.Map Int Alpaca) where toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 106
  • 107.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent instance ToCapture (Capture 'alpacaId' Int) where toCapture _ = DocCapture "alpacaId" "An id that uniquely identifies an alpaca in the system" instance ToSample (Alpaca) where toSamples _ = singleSample $ Alpaca "Dotty" White 0 instance ToSample (M.Map Int Alpaca) where toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 107
  • 108.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent instance ToCapture (Capture 'alpacaId' Int) where toCapture _ = DocCapture "alpacaId" "An id that uniquely identifies an alpaca in the system" instance ToSample (Alpaca) where toSamples _ = singleSample $ Alpaca "Dotty" White 0 instance ToSample (M.Map Int Alpaca) where toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 108
  • 109.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent instance ToCapture (Capture 'alpacaId' Int) where toCapture _ = DocCapture "alpacaId" "An id that uniquely identifies an alpaca in the system" instance ToSample (Alpaca) where toSamples _ = singleSample $ Alpaca "Dotty" White 0 instance ToSample (M.Map Int Alpaca) where toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 109
  • 110.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent instance ToCapture (Capture 'alpacaId' Int) where toCapture _ = DocCapture "alpacaId" "An id that uniquely identifies an alpaca in the system" instance ToSample (Alpaca) where toSamples _ = singleSample $ Alpaca "Dotty" White 0 instance ToSample (M.Map Int Alpaca) where toSamples _ = singleSample $ M.singleton 1 (Alpaca "Dotty" White 0) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 110
  • 111.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy import Servant.Docs apiDocs :: API apiDocs = docs alpacaApi main :: IO () main = (writeFile "docs.md" . markdown) apiDocs © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 111
  • 112.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy import Servant.Docs apiDocs :: API apiDocs = docs alpacaApi main :: IO () main = (writeFile "docs.md" . markdown) apiDocs © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 112
  • 113.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent alpacaApi :: Proxy AlpacaAPI alpacaApi = Proxy import Servant.Docs apiDocs :: API apiDocs = docs alpacaApi main :: IO () main = (writeFile "docs.md" . markdown) apiDocs © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 113
  • 114.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 114
  • 115.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 115
  • 116.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 116
  • 117.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 117
  • 118.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 118
  • 119.
    Actually writing theserver © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 119
  • 120.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 120
  • 121.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 121
  • 122.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 122
  • 123.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 123
  • 124.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 124
  • 125.
    type AlpacaAPI = "alapaca":> Get '[JSON] (M.Map Int Alpaca) :<|> "alapaca" :> Capture "alpacaId" Int :> Get '[JSON] Alpaca :<|> "alapaca" :> Capture "alpacaId" Int :> ReqBody '[JSON] Alpaca :> PutCreated '[JSON] NoContent fetchAll :: Monad m => m (M.Map Int Alpaca) fetchAll = pure $ M.singleton 1 dummy fetch :: Monad m => Int -> m Alpaca fetch id = pure dummy insert :: Monad m => Int -> Alpaca -> m NoContent insert id alpaca = pure NoContent © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 125
  • 126.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch :<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 126
  • 127.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch :<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 127
  • 128.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch --:<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 128
  • 129.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch --:<|> insert Example.hs:29:8: error: (...) Expected type: Server AlpacaAPI Actual type: Handler (M.Map Int Alpaca) :<|> (Int -> m0 Alpaca) (...) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 129
  • 130.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch :<|> insert app :: Application app = serve alpacaApi server main :: IO () main = run 8080 app © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 130
  • 131.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch :<|> insert app :: Application app = serve alpacaApi server main :: IO () main = run 8080 app © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 131
  • 132.
    server :: ServerAlpacaAPI server = fetchAll :<|> fetch :<|> insert app :: Application app = serve alpacaApi server main :: IO () main = run 8080 app © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 132
  • 133.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 133
  • 134.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 134
  • 135.
    Tools — aeson — optparse-generic —servant © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 135
  • 136.
    Tools — aeson — optparse-generic —servant — polysemy © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 136
  • 137.
    Tools — aeson — optparse-generic —servant — polysemy ! © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 137
  • 138.
    Tools — aeson — optparse-generic —servant — polysemy polysemy is a library for writing high-power, low-boilerplate, zero-cost, domain specific languages. It allows you to separate your business logic from your implementation details. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 138
  • 139.
    Tools — aeson — optparse-generic —servant — polysemy And in doing so, polysemy lets you turn your implementation code into reusable library code. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 139
  • 140.
    Sem r a. program:: Sem '[Console, (Random Int)] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 140
  • 141.
    Sem r a. program:: Sem '[Console, (Random Int)] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 141
  • 142.
    Sem r a. program:: Sem '[Console, (Random Int)] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 142
  • 143.
    Sem r a___ program:: Sem '[Console, (Random Int)] Int ___ .________________________________________^ © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 143
  • 144.
    Sem r a. program:: Sem '[Console, (Random Int)] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 144
  • 145.
    Sem r a________________________ program:: Sem '[Console, (Random Int)] Int ________________________ .________________________^ © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 145
  • 146.
    Sem r a. program:: Sem '[Console, (Random Int)] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 146
  • 147.
    Sem r aprogram:: Member Console r => Member (Random Int) r => Sem r Int . © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 147
  • 148.
    Sem r aprogram:: Member Console r => Member (Random Int) r => Sem r Int .________^ © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 148
  • 149.
    Sem r aprogram:: Member Console r <| => Member (Random Int) r <| => Sem r Int | .________^_____________________| © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 149
  • 150.
    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, @rabbitonweb, paul.szulc@gmail.com 150
  • 151.
    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, @rabbitonweb, paul.szulc@gmail.com 151
  • 152.
    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, @rabbitonweb, paul.szulc@gmail.com 152
  • 153.
    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, @rabbitonweb, paul.szulc@gmail.com 153
  • 154.
    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, @rabbitonweb, paul.szulc@gmail.com 154
  • 155.
    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, @rabbitonweb, paul.szulc@gmail.com 155
  • 156.
    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, @rabbitonweb, paul.szulc@gmail.com 156
  • 157.
    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, @rabbitonweb, paul.szulc@gmail.com 157
  • 158.
    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, @rabbitonweb, paul.szulc@gmail.com 158
  • 159.
    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, @rabbitonweb, paul.szulc@gmail.com 159
  • 160.
    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, @rabbitonweb, paul.szulc@gmail.com 160
  • 161.
    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, @rabbitonweb, paul.szulc@gmail.com 161
  • 162.
    -- cheatsheet printLine ::Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 162
  • 163.
    -- cheatsheet printLine ::Member Console r => String -> Sem r () readLine :: Member Console r => Sem r String nextRandom :: Member (Random v) r => Sem r v program :: Member Console r => Member (Random Int) r => Sem r Int program = do printLine "Insert your number:" i1 <- readLine i2 <- nextRandom pure (read i1 + i2) © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 163
  • 164.
    -- 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, @rabbitonweb, paul.szulc@gmail.com 164
  • 165.
    -- 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, @rabbitonweb, paul.szulc@gmail.com 165
  • 166.
    -- 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, @rabbitonweb, paul.szulc@gmail.com 166
  • 167.
    -- 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, @rabbitonweb, paul.szulc@gmail.com 167
  • 168.
    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, @rabbitonweb, paul.szulc@gmail.com 168
  • 169.
    run :: Sem'[] a -> a runM :: Monad m => Sem '[Embed m] a -> m a -- Sem '[Embed IO] a -> IO a program :: Member Console r => Member (Random Int) r => Sem r Int -- Sem '[Console, Random Int] Int © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 169
  • 170.
    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, @rabbitonweb, paul.szulc@gmail.com 170
  • 171.
    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, @rabbitonweb, paul.szulc@gmail.com 171
  • 172.
    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, @rabbitonweb, paul.szulc@gmail.com 172
  • 173.
    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, @rabbitonweb, paul.szulc@gmail.com 173
  • 174.
    What we needis an interpreter © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 174
  • 175.
    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, @rabbitonweb, paul.szulc@gmail.com 175
  • 176.
    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, @rabbitonweb, paul.szulc@gmail.com 176
  • 177.
    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, @rabbitonweb, paul.szulc@gmail.com 177
  • 178.
    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, @rabbitonweb, paul.szulc@gmail.com 178
  • 179.
    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, @rabbitonweb, paul.szulc@gmail.com 179
  • 180.
    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, @rabbitonweb, paul.szulc@gmail.com 180
  • 181.
    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, @rabbitonweb, paul.szulc@gmail.com 181
  • 182.
    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, @rabbitonweb, paul.szulc@gmail.com 182
  • 183.
    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, @rabbitonweb, paul.szulc@gmail.com 183
  • 184.
    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, @rabbitonweb, paul.szulc@gmail.com 184
  • 185.
    main :: IO() main = execute >>= putStrLn.show where execute = program & runConsoleIO & runRandomIO & runM © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 185
  • 186.
    main :: IO() main = execute >>= putStrLn.show where execute = program -- Sem '[Console, Random Int ] Int & runConsoleIO & runRandomIO & runM © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 186
  • 187.
    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, @rabbitonweb, paul.szulc@gmail.com 187
  • 188.
    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, @rabbitonweb, paul.szulc@gmail.com 188
  • 189.
    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, @rabbitonweb, paul.szulc@gmail.com 189
  • 190.
    Can we writeunit tests for this program? 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, @rabbitonweb, paul.szulc@gmail.com 190
  • 191.
    Can we writeunit tests for this program? 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, @rabbitonweb, paul.szulc@gmail.com 191
  • 192.
    Can we writeunit tests for this program? 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, @rabbitonweb, paul.szulc@gmail.com 192
  • 193.
    Can we writeunit tests for this program? 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, @rabbitonweb, paul.szulc@gmail.com 193
  • 194.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 194
  • 195.
    Can we writeunit tests for this program? 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, @rabbitonweb, paul.szulc@gmail.com 195
  • 196.
    runRandomConst :: Int-> Sem (Random Int ': r) a -> Sem r a runRandomConst v = interpret $ case NextRandom -> pure v runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a runConsoleConst constLine = interpret $ case PrintLine line -> pure () ReadLine -> pure constLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 196
  • 197.
    runRandomConst :: Int-> Sem (Random Int ': r) a -> Sem r a runRandomConst v = interpret $ case NextRandom -> pure v runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a runConsoleConst constLine = interpret $ case PrintLine line -> pure () ReadLine -> pure constLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 197
  • 198.
    runRandomConst :: Int-> Sem (Random Int ': r) a -> Sem r a runRandomConst v = interpret $ case NextRandom -> pure v runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a runConsoleConst constLine = interpret $ case PrintLine line -> pure () ReadLine -> pure constLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 198
  • 199.
    runRandomConst :: Int-> Sem (Random Int ': r) a -> Sem r a runRandomConst v = interpret $ case NextRandom -> pure v runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a runConsoleConst constLine = interpret $ case PrintLine line -> pure () ReadLine -> pure constLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 199
  • 200.
    runRandomConst :: Int-> Sem (Random Int ': r) a -> Sem r a runRandomConst v = interpret $ case NextRandom -> pure v runConsoleConst :: String -> Sem (Console ': r) a -> Sem r a runConsoleConst constLine = interpret $ case PrintLine line -> pure () ReadLine -> pure constLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 200
  • 201.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program & runConsoleConst "10" & runRandomConst 20 & run result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 201
  • 202.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program & runConsoleConst "10" & runRandomConst 20 & run result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 202
  • 203.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program -- Sem '[Console, Random Int] Int & runConsoleConst "10" & runRandomConst 20 & run result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 203
  • 204.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program -- Sem '[Console, Random Int] Int & runConsoleConst "10"-- Sem '[ Random Int] Int & runRandomConst 20 & run result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 204
  • 205.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program -- Sem '[Console, Random Int] Int & runConsoleConst "10"-- Sem '[ Random Int] Int & runRandomConst 20 -- Sem '[ ] Int & run result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 205
  • 206.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program -- Sem '[Console, Random Int] Int & runConsoleConst "10"-- Sem '[ Random Int] Int & runRandomConst 20 -- Sem '[ ] Int & run -- Int result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 206
  • 207.
    spec :: Spec spec= describe "program" $ do it "can add two numbers together (random and from console)" $ do let result = program -- Sem '[Console, Random Int] Int & runConsoleConst "10"-- Sem '[ Random Int] Int & runRandomConst 20 -- Sem '[ ] Int & run -- Int result `shouldBe` 30 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 207
  • 208.
    Typelevel Programming Magic vsScience © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 208
  • 209.
    Typelevel Programming: Magicvs Science — https://leanpub.com/thinking-with-types © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 209
  • 210.
    Typelevel Programming: Magicvs Science — https://leanpub.com/thinking-with-types — https://www.parsonsmatt.org/2017/04/26/ basic_type_level_programming_in_haskell.html © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 210
  • 211.
    Typelevel Programming: Magicvs Science — https://leanpub.com/thinking-with-types — https://www.parsonsmatt.org/2017/04/26/ basic_type_level_programming_in_haskell.html — https://github.com/tdietert/types-as-specifications © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 211
  • 212.
    What was thepoint of all this? © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 212
  • 213.
    Homework: Write anapplication that combines aeson, optparse- generic, servant and polysemy © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 213
  • 214.
    Polysemy - wherecan I learn more? © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 214
  • 215.
    Polysemy - wherecan I learn more? — Lets hack your homework together © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 215
  • 216.
    Polysemy - wherecan I learn more? — Lets hack your homework together — Prerequisites: hangouts, craft beer, haskell © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 216
  • 217.
    Polysemy - wherecan I learn more? — Lets hack your homework together © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 217
  • 218.
    Polysemy - wherecan I learn more? — Lets hack your homework together — https://github.com/rabbitonweb/todo-rest © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 218
  • 219.
    Polysemy - wherecan I learn more? — Lets hack your homework together — https://github.com/rabbitonweb/todo-rest — https://github.com/frost-org/frost © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 219
  • 220.
    Polysemy: conquer complexity ©Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 220
  • 221.
    Polysemy vs MTL/Taglessvs ... © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 221
  • 222.
    And The Sopranosis the show I recommend © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 222
  • 223.
    Because you neverreally know how it's gonna © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 223
  • 224.
    © Pawel Szulc,@rabbitonweb, paul.szulc@gmail.com 224
  • 225.
    Next year? :) ©Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 225
  • 226.
    Pawel Szulc twi!er: @rabbitonweb email:paul.szulc@gmail.com blog: https://rabbitonweb.com © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 226