Monads are a powerful abstraction in Haskell that provide a structured way to compose computations that may not be pure functions. See also: Common Monads, Monad Laws, Monad Transformers, Functors and Applicatives, Introduction to IO, Error Handling.
The Essence of Monads
A monad represents a computation with a specific context or effect:
Maybe- Computations that might fail (Common Monads, Error Handling)List- Non-deterministic computations with multiple results (Common Monads)IO- Computations with side effects (Introduction to IO)State- Computations that maintain state (Common Monads)- And many more…
The key insight is that monads provide a consistent interface for working with these contexts. See Monad Laws for the rules that all monads must follow.
The Monad Typeclass
The Monad typeclass is defined as:
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b -- pronounced "bind"
(>>) :: m a -> m b -> m b -- pronounced "then"
-- Default implementations
m >> n = m >>= \_ -> nNote: The Applicative constraint is part of the modern definition of the Monad typeclass. For this course, you can focus on return and (>>=). See Functors and Applicatives for more on the relationship between these abstractions.
Core Functions
return- Takes a value and wraps it in the monad(>>=)(bind) - Sequences two monadic operations, passing the result of the first to the second(>>)(then) - Sequences two monadic operations, discarding the result of the first
Understanding Bind (>>=)
The bind operator is the heart of monads:
(>>=) :: m a -> (a -> m b) -> m bIt allows us to:
- Take a monadic value (
m a) - Apply a function that takes a normal value and returns a monadic value (
a -> m b) - Get a new monadic value (
m b)
Think of it as “extracting” a value from a context, applying a function, and putting the result back into the context.
Example: The Maybe Monad
The Maybe type represents computations that might fail (Common Monads, Error Handling):
data Maybe a = Nothing | Just aIts monad instance:
instance Monad Maybe where
return x = Just x
Nothing >>= _ = Nothing
(Just x) >>= f = f xThis captures the idea that if any computation fails (returns Nothing), the entire sequence fails.
Example with Maybe
-- Safe division
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
-- Using bind to chain operations
computation :: Int -> Int -> Int -> Maybe Int
computation x y z =
safeDiv x y >>= \result1 ->
safeDiv result1 z >>= \result2 ->
return (result2 * 2)If either division fails, the entire computation returns Nothing.
Example: The List Monad
The list monad represents non-deterministic computations (Common Monads):
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)This captures the idea of exploring all possible outcomes.
Example with List
-- Generate all pairs from two lists
pairs :: [a] -> [b] -> [(a, b)]
pairs xs ys =
xs >>= \x ->
ys >>= \y ->
return (x, y)
-- Usage:
-- pairs [1,2] ['a','b'] == [(1,'a'),(1,'b'),(2,'a'),(2,'b')]do Notation
Haskell provides syntactic sugar called do notation to make working with monads more readable. See Monad Laws for how do notation is translated to uses of >>= and return.
computation :: Int -> Int -> Int -> Maybe Int
computation x y z = do
result1 <- safeDiv x y
result2 <- safeDiv result1 z
return (result2 * 2)Translation Rules
do notation is translated to uses of >>= and return:
| do notation | Translation |
|---|---|
do { x <- mx; rest } | mx >>= \x -> do { rest } |
do { let x = v; rest } | let x = v in do { rest } |
do { mx; rest } | mx >> do { rest } |
do { mx } | mx |
The Identity Monad
The simplest monad is the Identity monad, which adds no computational effects. See Monad Transformers for how this is used as a base for more complex monads.
newtype Identity a = Identity { runIdentity :: a }
instance Monad Identity where
return x = Identity x
(Identity x) >>= f = f xIt’s useful primarily for understanding monadic concepts and as a base for monad transformers.
Pattern: Building Computations with Monads
Monads allow us to build complex computations by sequencing smaller ones. This works for any monad, whether Maybe, List, IO, etc. (Common Monads, Introduction to IO)
complexComputation :: Monad m => m a -> m b -> (a -> b -> m c) -> m c
complexComputation ma mb combine = do
a <- ma
b <- mb
combine a bUsing Monads for Different Contexts
Different monads handle different contexts (Common Monads, Error Handling, Introduction to IO).
-- Maybe: Handle potential failure
findUser :: UserId -> Maybe User
validateInput :: Input -> Maybe ValidInput
process :: UserId -> Input -> Maybe Result
process uid input = do
user <- findUser uid
validInput <- validateInput input
return (computeResult user validInput)
-- List: Non-deterministic computation
possibleMoves :: GameState -> [Move]
possibleOutcomes :: GameState -> [GameState]
possibleOutcomes state = do
move <- possibleMoves state
return (applyMove state move)
-- IO: Sequential side effects
getUserData :: IO UserData
processUserData :: UserData -> IO Result
program :: IO Result
program = do
userData <- getUserData
processUserData userDataKey Points to Remember
- A monad represents a computation with a specific context or effect
- The
Monadtypeclass defines a consistent interface for these contexts - The two key functions are
return(wrap a value) and>>=(sequence operations) donotation provides syntactic sugar for monadic operations- Different monads handle different kinds of computational contexts
- The Monad pattern allows for composing complex computations from simpler ones