A walk through a small project (a REST command line client) that aims at explaining monads and at providing a use case for monad transformers.
Shown at Functional Programmers Meetup Paris 2015.
5. Introduction
What I Wish I Knew When Learning Haskell
1. Don’t read the monad tutorials.
2. No really, don’t read the monad tutorials.
2 / 34
6. Introduction
What I Wish I Knew When Learning Haskell
1. Don’t read the monad tutorials.
2. No really, don’t read the monad tutorials.
…
2 / 34
7. Introduction
What I Wish I Knew When Learning Haskell
1. Don’t read the monad tutorials.
2. No really, don’t read the monad tutorials.
…
8. Don’t write monad-analogy tutorials.
2 / 34
9. Typeclass
class Monad m where
return :: a → m a
(>>=) :: m a → (a → m b) → m b
adit.io
3 / 34
10. Typeclass
class Monad m where
return :: a → m a
(>>=) :: m a → (a → m b) → m b
(>>) :: Monad m ⇒ m a → m b → m b
(>=>) :: Monad m ⇒ (a → m b)
→ (b → m c)
→ (a → m c)
3 / 34
17. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
7 / 34
18. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
List allNextTurns :: Board → [Board]
7 / 34
19. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
List allNextTurns :: Board → [Board]
IO httpGet :: Url → IO String
7 / 34
20. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
List allNextTurns :: Board → [Board]
IO httpGet :: Url → IO String
Reader r runReader :: Reader r a → r → a
7 / 34
21. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
List allNextTurns :: Board → [Board]
IO httpGet :: Url → IO String
Reader r runReader :: Reader r a → r → a
Writer w runWriter :: Writer w a → (a, w)
7 / 34
22. ALL TEH MONADS
Maybe getUser :: Id → Maybe User
Either a parse :: String → Either Error Value
List allNextTurns :: Board → [Board]
IO httpGet :: Url → IO String
Reader r runReader :: Reader r a → r → a
Writer w runWriter :: Writer w a → (a, w)
State s runState :: Reader s a → s → (a, s)
7 / 34
24. Reader example
data Conf = { color :: Color, size :: Int }
formatter :: String → Reader Conf String
formatter p = do
c ← asks color
s ← asks size
return (undefined) -- TODO
format :: Conf → String → String
format c s = runReader (textFormatter s) c
9 / 34
26. Writer example
type Log = [String]
type Debug = Writer Log
value :: Int → Debug Int
value x = Writer (x, ["Value"])
plus :: Debug Int → Debug Int → Debug Int
plus x y = do
a ← x
b ← y
Writer (a + b, "Plus")
11 / 34
28. State Example
data GameInfo = GameInfo {
turn :: Int,
score :: Int
}
skipTurn :: State GameInfo ()
skipTurn = do
t ← gets turn
s ← gets score
put $ GameInfo (t + 1) (s `div` 2)
13 / 34
31. Monadic syntax?
main = run $ do
setServerUrl "http://server.com/api"
setCredentials "guest" "guest"
authenticate
result ← request "server.listMyStuff"
print $ format result
16 / 34
39. All the way down
▶ Monad generalization
▶ Extra monad parameter
▶ Monad stacks
20 / 34
40. Monad → MonadT
data State s a = { ... }
get :: State s s
put :: s → State s ()
Mister
21 / 34
41. Monad → MonadT
data StateT s m a = { ... }
get :: MonadState s m ⇒ m s
put :: MonadState s m ⇒ s → m ()
MisterT
21 / 34
42. Lifting
example :: CustomT IO Int
example = do
customStuff 1
lift $ putStrLn "Turtles!"
customStuff 2
22 / 34
43. Lifting
example :: CustomT (MaybeT IO) Int
example = do
customStuff 1
lift $ lift $ putStrLn "Turtles!"
customStuff 2
22 / 34
44. Instances
class Monad m ⇒ MonadState s m where
get :: m s
put :: s → m ()
instance MonadState s m ⇒
MonadState s (ReaderT r m) where
get = lift get
put = lift . put
23 / 34
47. Handling errors: ErrorT
type ErrorMsg = String
type Result m a = m (Either ErrorMsg a)
type RestT m = ErrorT ErrorMsg m
..
m
.
ErrorT ErrorMsg a
26 / 34
48. Handling errors: ErrorT
type ErrorMsg = String
type Result m a = m (Either ErrorMsg a)
type RestT m = ErrorT ErrorMsg m
run :: RestT m a → Result m a
run = runErrorT
..
m
.
ErrorT ErrorMsg a
26 / 34
49. Handling errors: ErrorT
type ErrorMsg = String
type Result m a = m (Either ErrorMsg a)
type RestT m = ErrorT ErrorMsg m
run :: RestT m a → Result m a
run = runErrorT
liftRest :: m a → RestT m a
liftRest = lift
..
m
.
ErrorT ErrorMsg a
26 / 34
50. Adding session: StateT
type ErrorMsg = String
type Token = String
type Result m a = m (Either ErrorMsg a)
type RestT m = StateT Token (ErrorT ErrorMsg m)
..
m
.
ErrorT ErrorMsg
.
StateT Token a
27 / 34
51. Adding session: StateT
type ErrorMsg = String
type Token = String
type Result m a = m (Either ErrorMsg a)
type RestT m = StateT Token (ErrorT ErrorMsg m)
run :: Token → RestT m a → Result m a
run t x = runErrorT $ evalStateT x t
..
m
.
ErrorT ErrorMsg
.
StateT Token a
27 / 34
52. Adding session: StateT
type ErrorMsg = String
type Token = String
type Result m a = m (Either ErrorMsg a)
type RestT m = StateT Token (ErrorT ErrorMsg m)
run :: Token → RestT m a → Result m a
run t x = runErrorT $ evalStateT x t
liftRest :: m a → RestT m a
liftRest = lift . lift
..
m
.
ErrorT ErrorMsg
.
StateT Token a
27 / 34
53. Dependency injection: ReaderT
type ErrorMsg = String
type Token = String
type Getter m = String → m String
type Result m a = m (Either ErrorMsg a)
type RestT m = ReaderT (Getter m) (StateT Token (ErrorT ErrorMsg m))
..
m
.
ErrorT ErrorMsg
.
StateT Token
.
ReaderT (Getter m) a
28 / 34
54. Dependency injection: ReaderT
type ErrorMsg = String
type Token = String
type Getter m = String → m String
type Result m a = m (Either ErrorMsg a)
type RestT m = ReaderT (Getter m) (StateT Token (ErrorT ErrorMsg m))
run :: Getter m → Token → RestT m a → Result m a
run g t x = runErrorT $ evalStateT (runReaderT x g) t
..
m
.
ErrorT ErrorMsg
.
StateT Token
.
ReaderT (Getter m) a
28 / 34
55. Dependency injection: ReaderT
type ErrorMsg = String
type Token = String
type Getter m = String → m String
type Result m a = m (Either ErrorMsg a)
type RestT m = ReaderT (Getter m) (StateT Token (ErrorT ErrorMsg m))
run :: Getter m → Token → RestT m a → Result m a
run g t x = runErrorT $ evalStateT (runReaderT x g) t
liftRest :: m a → RestT m a
liftRest = lift . lift . lift
..
m
.
ErrorT ErrorMsg
.
StateT Token
.
ReaderT (Getter m) a
28 / 34
56. YAY!
request :: String → RestT m String
request method = do
getter ← ask
token ← get
let q = query method token
liftRest $ getter q
29 / 34
57. AT LAST!
authenticate :: RestT m ()
authenticate = do
token ← get
unless (null token)
(throwError "already logged in")
answer ← request "getToken"
token ← parse answer
put token
30 / 34
58. WOOOO!
main = void $ run httpGet emptyToken $ do
authenticate
answer ← request "showMyStuff"
liftIO $ print answer
31 / 34