SlideShare a Scribd company logo
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

More Related Content

DOCX
2 a networkflow
PDF
WordPressでIoTをはじめよう
PPT
computer notes - Data Structures - 3
PDF
ZeroMQ Is The Answer: DPC 11 Version
PPTX
Peggy elasticsearch應用
PDF
Un dsl pour ma base de données
PDF
How to stand on the shoulders of giants
DOCX
Binomial heap
2 a networkflow
WordPressでIoTをはじめよう
computer notes - Data Structures - 3
ZeroMQ Is The Answer: DPC 11 Version
Peggy elasticsearch應用
Un dsl pour ma base de données
How to stand on the shoulders of giants
Binomial heap

Similar to Painless Haskell (10)

PPTX
Mule parsing with json
PDF
An Introduction to Scala (2014)
PDF
PureScript & Pux
PDF
sbt, history of JSON libraries, microservices, and schema evolution (Tokyo ver)
PDF
A Sceptical Guide to Functional Programming
PDF
Scala in Places API
PDF
Groovy for java developers
PDF
Grooscript in Action SpringOne2gx 2015
PPT
How Groovy Helps
Mule parsing with json
An Introduction to Scala (2014)
PureScript & Pux
sbt, history of JSON libraries, microservices, and schema evolution (Tokyo ver)
A Sceptical Guide to Functional Programming
Scala in Places API
Groovy for java developers
Grooscript in Action SpringOne2gx 2015
How Groovy Helps
Ad

More from Pawel Szulc (20)

PDF
Getting acquainted with Lens
PDF
Impossibility
PDF
Maintainable Software Architecture in Haskell (with Polysemy)
PDF
Trip with monads
PDF
Trip with monads
PDF
Illogical engineers
PDF
RChain - Understanding Distributed Calculi
PDF
Illogical engineers
PDF
Understanding distributed calculi in Haskell
PDF
Software engineering the genesis
PDF
Make your programs Free
PDF
Going bananas with recursion schemes for fixed point data types
PDF
“Going bananas with recursion schemes for fixed point data types”
PDF
Writing your own RDD for fun and profit
PDF
The cats toolbox a quick tour of some basic typeclasses
PDF
Introduction to type classes
PDF
Functional Programming & Event Sourcing - a pair made in heaven
PDF
Apache spark workshop
PDF
Introduction to type classes in 30 min
PDF
Real world gobbledygook
Getting acquainted with Lens
Impossibility
Maintainable Software Architecture in Haskell (with Polysemy)
Trip with monads
Trip with monads
Illogical engineers
RChain - Understanding Distributed Calculi
Illogical engineers
Understanding distributed calculi in Haskell
Software engineering the genesis
Make your programs Free
Going bananas with recursion schemes for fixed point data types
“Going bananas with recursion schemes for fixed point data types”
Writing your own RDD for fun and profit
The cats toolbox a quick tour of some basic typeclasses
Introduction to type classes
Functional Programming & Event Sourcing - a pair made in heaven
Apache spark workshop
Introduction to type classes in 30 min
Real world gobbledygook
Ad

Recently uploaded (20)

PDF
System and Network Administraation Chapter 3
PPTX
Transform Your Business with a Software ERP System
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
L1 - Introduction to python Backend.pptx
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PPTX
Introduction to Artificial Intelligence
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PPTX
ai tools demonstartion for schools and inter college
System and Network Administraation Chapter 3
Transform Your Business with a Software ERP System
CHAPTER 2 - PM Management and IT Context
Adobe Illustrator 28.6 Crack My Vision of Vector Design
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
Odoo Companies in India – Driving Business Transformation.pdf
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Which alternative to Crystal Reports is best for small or large businesses.pdf
Wondershare Filmora 15 Crack With Activation Key [2025
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Upgrade and Innovation Strategies for SAP ERP Customers
L1 - Introduction to python Backend.pptx
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
How to Choose the Right IT Partner for Your Business in Malaysia
Introduction to Artificial Intelligence
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Design an Analysis of Algorithms II-SECS-1021-03
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
ai tools demonstartion for schools and inter college

Painless Haskell

  • 1. 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
  • 2. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 2
  • 3. Any sufficiently advanced technology is 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 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 5
  • 6. Tools — aeson A JSON parsing and encoding library optimized for ease of use and high performance. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 6
  • 7. 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
  • 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 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 34
  • 35. Tools — aeson — optparse-generic © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 35
  • 36. 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
  • 37. 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
  • 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 more Magic © 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  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
  • 72. Plan B © Pawel Szulc, @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 © Pawel Szulc, @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 the server © 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 :: Server AlpacaAPI server = fetchAll :<|> fetch :<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 126
  • 127. server :: Server AlpacaAPI server = fetchAll :<|> fetch :<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 127
  • 128. server :: Server AlpacaAPI server = fetchAll :<|> fetch --:<|> insert © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 128
  • 129. 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
  • 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 130
  • 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 131
  • 132. 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
  • 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 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
  • 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 151
  • 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 152
  • 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 153
  • 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 154
  • 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 155
  • 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 156
  • 157. 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
  • 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 158
  • 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 159
  • 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 160
  • 161. 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
  • 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 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
  • 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 need is an interpreter © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 174
  • 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 175
  • 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 176
  • 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 177
  • 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 178
  • 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 179
  • 180. 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
  • 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 -> putStrLn line ReadLine -> getLine © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 181
  • 182. 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
  • 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 183
  • 184. 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
  • 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 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
  • 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 191
  • 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 192
  • 193. 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
  • 194. © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 194
  • 195. 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
  • 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 vs Science © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 208
  • 209. Typelevel Programming: Magic vs Science — https://leanpub.com/thinking-with-types © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 209
  • 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 © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 210
  • 211. 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
  • 212. What was the point of all this? © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 212
  • 213. Homework: Write an application that combines aeson, optparse- generic, servant and polysemy © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 213
  • 214. Polysemy - where can I learn more? © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 214
  • 215. Polysemy - where can I learn more? — Lets hack your homework together © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 215
  • 216. Polysemy - where can I learn more? — Lets hack your homework together — Prerequisites: hangouts, craft beer, haskell © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 216
  • 217. Polysemy - where can I learn more? — Lets hack your homework together © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 217
  • 218. 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
  • 219. 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
  • 220. Polysemy: conquer complexity © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 220
  • 221. Polysemy vs MTL/Tagless vs ... © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 221
  • 222. And The Sopranos is the show I recommend © Pawel Szulc, @rabbitonweb, paul.szulc@gmail.com 222
  • 223. Because you never really 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