3. What are we going to do here?
• Understand why custom monads are useful
• Learn the different elements in a custom monad
• Practice how to create them in various styles
• Final tagless / MTL style
• Initial style / Free monads
1
4. What are we going to do here?
• Understand why custom monads are useful
• Learn the different elements in a custom monad
• Practice how to create them in various styles
• Final tagless / MTL style
• Initial style / Free monads
Talk! Think! Code!
1
5. Who am I?
Alejandro / @trupill in Twitter / @serras in GitHub
• Associate Professor at Utrecht University, NL
• Teacher of FP courses at various levels
• Researcher on FP topics, mostly Haskell
• Writer of books about Haskell
• Beginning Haskell, published by Apress
• The Book of Monads, to appear soon
• Advocate of strong typing in programming languages
• Haskell, Agda, Idris, LiquidHaskell, …
• Secretly in love with Lisp and macros
2
6. Talk! Think! Code!
As part of the workshop, we discuss some exercises
• Work together with your neighbour!
• 5-7 minutes work + 5-7 minutes discussion
Some (but not all) exercises involve coding
• Haskell? Scala? Whitespace? I don’t care!
• Your language must support higher-kinded abstraction
Prerequisite: some experience with monads
3
8. The path to monads
1. Discover the most common monads
• State, Maybe, Reader, Writer…
2. Learn operations shared among them
• mapM, sequence, …
3. Work hard when you require several of them
• Usually using monad transformers
4
9. Benefits
• Common operations
• Once again, mapM, sequence, …
• Functor, Applicative, Traversable
• Compiler support
• do notation, for comprehensions
• Well-known set of primitive blocks
5
13. Abstraction
Capture your domain better
FP provides powerful tools to capture data and their invariants
• Algebraic data types and pattern matching
• Strong type systems
• Contracts for dynamically-typed languages
What about processes / services?
7
14. Abstraction
Capture your domain better
FP provides powerful tools to capture data and their invariants
• Algebraic data types and pattern matching
• Strong type systems
• Contracts for dynamically-typed languages
What about processes / services?
Custom monads!
7
15. My domain: Tic-Tac-Toe
• Query the state of the board
data Position = Position ...
data Player = O | X deriving (Eq, Ord)
info :: Position -> TicTacToe (Maybe Player)
• Indicate that we want to claim an empty position
• Know whether we have already won or lost
data Result = AlreadyTaken { by :: Player }
| NextTurn
| GameEnded { winner :: Player }
take :: Position -> TicTacToe Result
8
16. Safety
Ensure that only a subset of operations of a big monad is
available in a certain computation
• Usually, the big monad is IO
Some examples:
• Talk only to the database
• Restrict console output to logging
9
19. Time to think #1
Talk to each other and find:
• Example of a domain to model
• Processes or services in that domain
• Not too complicated, few primitives
11
21. Syntax and Semantics
In our custom monads we usually separate:
• The description of computations
• Including the available primitives
• From the execution of the computations
In State, Maybe, … those are intertwined
12
22. Syntax / Algebra
1. Primitive operations for your monad
2. Combinators to describe computations
• In addition to the monadic ones
In summary, how to describe behaviors in your domain
13
23. Syntax / Algebra
1. Primitive operations for your monad
2. Combinators to describe computations
• In addition to the monadic ones
In summary, how to describe behaviors in your domain
“API” for your monad
info :: Position -> TicTacToe (Maybe Player)
take :: Position -> TicTacToe Result
13
24. Semantics / Interpretations / Handlers
Execute the computations describes by the syntax
• Usually mapping each primitive to an action
14
25. Semantics / Interpretations / Handlers
Execute the computations describes by the syntax
• Usually mapping each primitive to an action
More than one interpretation is possible
For example, for our TicTacToe monad
• Run with console IO
• Run on a graphical UI over a network
• Mock execution for tests
14
26. Time to think #2
Consider the domain from the first exercise and:
• Make the syntax more concrete
• Give an API for your primitives
• Think of a derived operation using those primitives
• Describe one or more interpretations
15
28. Three different styles
• Final style / Object algebras / MTL style
• Initial style
• Pattern abstracted into free monads
• Operational style
• Pattern abstracted into freer monads
16
29. Three different styles
• Final style / Object algebras / MTL style
• Initial style
• Pattern abstracted into free monads
• Operational style
• Pattern abstracted into freer monads
Initial and operational are fairly similar
16
30. Three different styles
For each of the style we need to say how to:
1. Define the syntax of the monad
2. Give a nicer syntax for the API, if required
3. Write computations and which type they get
4. Define interpretations
17
33. Final style: syntax
Use a type class or a trait
• Each primitive operation is a method in the class
• Positions where the monad would appear are replaced by
the variable heading the class
class TicTacToe m where
info :: Position -> m (Maybe Player)
take :: Position -> m Result
trait TicTacToe[F[_]] {
def info(p: Position): F[Option[Player]]
def take(p: Position): F[Result]
}
19
34. Final style: computations
Simply use the monad combinators and the primitives
takeIfNotTaken p = do
i <- info p
case i of
Just _ -> return Nothing
Nothing -> Just <$> take p
20
35. Final style: computations
Simply use the monad combinators and the primitives
takeIfNotTaken p = do
i <- info p
case i of
Just _ -> return Nothing
Nothing -> Just <$> take p
The type reflects that you need a monad which supports the
TicTacToe operations
takeIfNotTaken :: (Monad m, TicTacToe m)
=> Position -> m (Maybe Result)
20
36. Why keep Monad separate from TicTacToe?
Another possibility, closer to the mtl library:
class Monad m => TicTacToe m where
info :: Position -> m (Maybe Player)
take :: Position -> m Result
21
37. Why keep Monad separate from TicTacToe?
Another possibility, closer to the mtl library:
class Monad m => TicTacToe m where
info :: Position -> m (Maybe Player)
take :: Position -> m Result
As a result, computations get a “cleaner” type:
takeIfNotTaken :: TicTacToe m -- No Monad m
=> Position -> m (Maybe Result)
21
38. Why keep Monad separate from TicTacToe?
Another possibility, closer to the mtl library:
class Monad m => TicTacToe m where
info :: Position -> m (Maybe Player)
take :: Position -> m Result
As a result, computations get a “cleaner” type:
takeIfNotTaken :: TicTacToe m -- No Monad m
=> Position -> m (Maybe Result)
Personally, I prefer to keep them separate:
• Sometimes Applicative is enough
• But with this approach Monad infects everything
21
39. Final style: interpretations
Each interpretation is an instance of the type class
type Board = Map Position Player
type RPSBI = ReaderT Player (StateT Board IO)
instance TicTacToe RPSBI where
info p = lookup p <$> get
take p = do
l <- info p
case l of
Just p' -> return AlreadyTaken { by = p' }
Nothing -> do me <- ask
modify (insert p me)
liftIO (putStrLn "Your next move:")
... 22
40. Final style: interpretations
For “safety” monads we map into the larger monad
class Logging m where
log :: String -> m ()
instance Logging IO where
log = putStrLn
23
41. Final style: interpretations
For “safety” monads we map into the larger monad
class Logging m where
log :: String -> m ()
instance Logging IO where
log = putStrLn
Q: what is the implementation of…?
run :: (forall m. Logging m => m a) -> IO a
23
42. Final style: interpretations
For “safety” monads we map into the larger monad
class Logging m where
log :: String -> m ()
instance Logging IO where
log = putStrLn
Q: what is the implementation of…?
run :: (forall m. Logging m => m a) -> IO a
run x = x
23
43. Time to code #3
Implement your monad in final style:
• Define the type class or trait
• Implement the derived operation from #2
• Sketch an interpretation
24
45. Initial style: syntax
Use a data type with continuations
• For each primitive operation op :: A -> B -> ... -> M Z
• Create a new constructor in the data type
• Add fields of types A, B, …
• The last argument is a function Z -> M a which refers back
to the monad (that is, a continuation)
• Also, we have a constructor with a single field a
• Represents the return of the monad
25
46. Initial style: example
The primitive operations of our TicTacToe monad:
info :: Position -> TicTacToe (Maybe Player)
take :: Position -> TicTacToe Result
26
47. Initial style: example
The primitive operations of our TicTacToe monad:
info :: Position -> TicTacToe (Maybe Player)
take :: Position -> TicTacToe Result
The TicTacToe data type is defined as:
data TicTacToe a
= Info Position (Maybe Player -> TicTacToe a)
| Take Position (Result -> TicTacToe a)
| Done a
In Scala the code would be slightly longer…
26
48. Is this thing even a Monad?
Let’s build it step by step using GHC holes…
27
49. Is this thing even a Monad?
This is the final result:
instance Monad TicTacToe where
return = Done
(Done x) >>= f = f x
(Info p k) >>= f = Info p (pl -> k pl >>= f)
(Take p k) >>= f = Take p (r -> k r >>= f)
28
50. Reminder of a computation in final style
takeIfNotTaken p = do
i <- info p
case i of
Just _ -> return Nothing
Nothing -> Just <$> take p
How would this look in initial style?
29
51. Initial style: computations
takeIfNotTaken p = do
i <- info p
case i of
Just _ -> return Nothing
Nothing -> Just <$> take p
We have to deal with continuations ourselves
• Without any help of do notation
takeIfNotTaken p =
Info p $ i ->
case i of
Just _ -> Done Nothing
Nothing -> Take p $ r -> Done (Just r)
30
52. Initial style: smart constructors
This is a trick to get versions of the primitive operations which
we can combine using do notation
31
53. Initial style: smart constructors
This is a trick to get versions of the primitive operations which
we can combine using do notation
This is what we have:
Info :: Position -> (Maybe Player -> TicTacToe a)
-> TicTacToe a
and this is what we want as our API:
info :: Position -> TicTacToe (Maybe Player)
31
54. Initial style: smart constructors
This is a trick to get versions of the primitive operations which
we can combine using do notation
This is what we have:
Info :: Position -> (Maybe Player -> TicTacToe a)
-> TicTacToe a
and this is what we want as our API:
info :: Position -> TicTacToe (Maybe Player)
info p = Info p return
-- return :: Maybe Player -> TicTacToe (Maybe Player)
31
55. Initial style: interpretations
Interpretations are usually (but not always) natural
transformations from our custom monad to another monad
runGame :: TicTacToe a -> RPSBI a
def runGame :: TicTacToe ~> RPSBI
32
56. Initial style: interpretations
Interpretations are usually (but not always) natural
transformations from our custom monad to another monad
runGame :: TicTacToe a -> RPSBI a
def runGame :: TicTacToe ~> RPSBI
runGame (Done x) = return x
runGame (Info p k) = do pl <- lookup p <$> get
runGame (k pl)
runGame (Take p k) = do pl <- lookup p <$> get
case pl of
Just p' ->
runGame (k $ AlreadyTaken p')
Nothing -> ...
32
57. Continuations in interpretations
Note the similarities between the two operations:
runGame (Info p k) = do ...
runGame (k pl)
runGame (Take p k) = do ...
runGame (k $ AlreadyTaken p')
Always the last line on each branch:
• Calls the continuations with some data
• Recursively calls runGame
33
58. Time to code #4
Implement your monad in initial style:
• Define the corresponding data type and its Monad instance
• Implement the derived operation from #2 using
continuations manually
• Write the smart constructor for each primitive and
implement the derived operation again
34
60. All initial style monads are created equal
• The data type always has a constructor for return
• The other constructors follow a similar shape
• Smart constructors are obtained by using return as the
continuation
• Interpretations into other monads have the same
recursive structure
35
61. All initial style monads are created equal
• The data type always has a constructor for return
• The other constructors follow a similar shape
• Smart constructors are obtained by using return as the
continuation
• Interpretations into other monads have the same
recursive structure
How to abstract these commonalities? Free monads!
35
62. Free monads: warning
My notion of free monads abstract over initial style
• Implemented in Haskell’s free package
• Corresponds to the categorical notion of free monad of a
functor
Scalaz and Cats Free abstract over operational style
• In Haskell this is known as the freer monad and can be
found in the operational package
• Very similar, and easier to use
36
63. Separate the common from the specific
data TicTacToe a
= Info Position (Maybe Player -> TicTacToe a)
| Take Position (Result -> TicTacToe a)
| Done a
-- Specific to TicTacToe
data TicTacToeF r
= Info Position (Maybe Player -> r)
| Take Position (Result -> r)
-- Common to all initial style monads
data Free f a
= Free (f (Free f a))
| Pure a
-- All together now
type TicTacToe = Free TicTacToeF 37
64. Free monad of a functor
Let us have a peek into the Monad instance of Free
instance Functor f => Monad (Free f) where
return = Pure
Pure x >>= f = f x
Free x >>= f = Free (fmap (>>= f) x)
So we need TicTacToeF to be a functor!
38
65. Free monad of a functor
Let us have a peek into the Monad instance of Free
instance Functor f => Monad (Free f) where
return = Pure
Pure x >>= f = f x
Free x >>= f = Free (fmap (>>= f) x)
So we need TicTacToeF to be a functor!
• These data types are always functors
• GHC can derive the Functor instance for us
data TicTacToeF r = ... deriving Functor
38
66. Smart constructors for free monads
Now what we have is slightly different:
Info :: Position -> (Maybe Player -> r)
-> TicTacToeF r
And also what we want to achieve:
info :: Position -> Free TicTacToeF (Maybe Player)
39
67. Smart constructors for free monads
Now what we have is slightly different:
Info :: Position -> (Maybe Player -> r)
-> TicTacToeF r
And also what we want to achieve:
info :: Position -> Free TicTacToeF (Maybe Player)
info p = Free _
-- _ has type
-- TicTacToeF (Free TicTacToeF (Maybe Player))
39
68. Smart constructors for free monads
Now what we have is slightly different:
Info :: Position -> (Maybe Player -> r)
-> TicTacToeF r
And also what we want to achieve:
info :: Position -> Free TicTacToeF (Maybe Player)
info p = Free (Info p _)
-- _ has type
-- (Maybe Player -> Free TicTacToeF (Maybe Player))
40
69. Smart constructors for free monads
Now what we have is slightly different:
Info :: Position -> (Maybe Player -> r)
-> TicTacToeF r
And also what we want to achieve:
info :: Position -> Free TicTacToeF (Maybe Player)
info p = Free (Info p return)
-- Almost as before!
-- :)
41
70. Interpretations for free monads
We only need to provide the specifics of each operation:
runGame' :: TicTacToeF a -> RPSBI a
runGame' (Info p k) = do pl <- lookup p <$> get
return (k pl)
runGame' (Take p k) = ...
42
71. Interpretations for free monads
We only need to provide the specifics of each operation:
runGame' :: TicTacToeF a -> RPSBI a
runGame' (Info p k) = do pl <- lookup p <$> get
return (k pl)
runGame' (Take p k) = ...
The recursive part is tied using foldFree:
foldFree :: Monad m => (forall r. f r -> m r)
-> Free f a -> m a
foldFree _ (Pure x) = return x
foldFree f (Free x) = f x >>= foldFree f
runGame = foldFree runGame'
42
72. Time to code #5
Rewrite your initial style monad as a free monad
• Define the functor for your primitives
• Rewrite the smart constructors
• Enjoy the reduction in lines!
43
74. Combining several monads
Final style monads are very easy to combine
fileAndNetwork :: (Monad m, FS m, Network m)
=> URL -> FilePath -> m Result
44
75. Combining several monads
Final style monads are very easy to combine
fileAndNetwork :: (Monad m, FS m, Network m)
=> URL -> FilePath -> m Result
Initial style monads are impossible to combine
• But we can combine them if they are free monads
fileAndNetwork :: URL -> FilePath
-> Free (FSF :+: NetworkF) Result
44
76. Combining functors
The technique used to combine the operations from two
functors is called Data types à la carte
data (f :+: g) a = InL (f a) | InR (g a)
Although getting good syntax is more complicated
45
77. Performance
Peformance of free monads degrades with left-nested binds
• Akin to the problem of left-nested concatenation in lists
46
78. Performance
Peformance of free monads degrades with left-nested binds
• Akin to the problem of left-nested concatenation in lists
Several solutions have been proposed:
• Church and Scott encodings
• Codensity transformation
• Type-aligned sequences / “Reflection without Remorse”
But there is no clear winner for all cases
46
79. Summary
• Custom monads help us modelling behaviors in our
domain
• There are several styles to build them
• Final: using type classes
• Initial: using data types
• Free monads abstract the commonalities
• Operational: not treated today
47
83. Why “MTL style”?
MTL = Monad Transformer Library
• Supports working with monad stacks polymorphically
• Defines classes MonadState, MonadReader, and so on
• Programmers may mix primitives from several monads in
one computation
increaseAndLog :: (MonadState Int m, MonadWriter String m)
=> Int -> m Int
increaseAndLog n = do
s <- get
let new = s + n
put new
tell $ "New value: " ++ show new ++ "n"
49
84. Smart constructors, generically
The free package provides another combinator:
liftF :: Functor f => f a -> Free f a
liftF = Free . fmap return
Using liftF you can write instead:
info p = liftF (Info p id)
50