For a type to be a proper monad, it must satisfy three laws that ensure it behaves consistently and predictably. These laws are mathematical properties that should hold for any monad implementation. See also: Monads Basics, Common Monads, Monad Transformers, Equational Reasoning.
The Three Monad Laws
1. Left Identity
return a >>= f = f aThis law states that if we take a value, wrap it in a monad using return, and then feed it to a function using >>=, the result should be the same as just applying the function directly to the value. For more on return and >>=, see Monads Basics.
Intuition
return is a “neutral” way to inject a value into a monad. It shouldn’t add any computational effect or change the value. So using return and then immediately extracting the value with >>= should be equivalent to not using the monad at all.
Example with Maybe
return 5 >>= (\x -> Just (x + 3))
-- is the same as
(\x -> Just (x + 3)) 5
-- Both evaluate to Just 82. Right Identity
m >>= return = mThis law states that if we have a monadic value and we feed its result to return using >>=, we should get back the original monadic value.
Intuition
Since return simply injects a value into the monad without adding any effects, taking a monadic value, extracting its core value, and then reinjecting it with return should give us back what we started with.
Example with List
[1, 2, 3] >>= return
-- is the same as
[1, 2, 3]3. Associativity
(m >>= f) >>= g = m >>= (\x -> f x >>= g)This law states that the order of application doesn’t matter when chaining monadic operations. Whether we first apply f to m and then apply g to the result, or we define a new function that applies f and then g, and apply that to m, the result should be the same. See Monads Basics for how this relates to do notation.
Intuition
This ensures that when we chain monadic operations, we can group them however we want without affecting the result. This is essential for building complex computations out of simpler ones in a modular way.
Example with IO
-- These two expressions are equivalent:
(getLine >>= readFile) >>= putStrLn
getLine >>= (\filename -> readFile filename >>= putStrLn)Verifying the Laws for Specific Monads
Let’s verify the monad laws for a few common monads to better understand them. See Common Monads for more on these types.
Maybe Monad
The Maybe monad is defined as:
instance Monad Maybe where
return = Just
Nothing >>= _ = Nothing
(Just x) >>= f = f xLeft Identity
return a >>= f
= Just a >>= f
= f aRight Identity
m >>= returnFor m = Nothing:
Nothing >>= return
= NothingFor m = Just a:
Just a >>= return
= return a
= Just aAssociativity
(m >>= f) >>= gFor m = Nothing:
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
Nothing >>= (\x -> f x >>= g)
= NothingFor m = Just a:
(Just a >>= f) >>= g
= f a >>= g
Just a >>= (\x -> f x >>= g)
= (\x -> f x >>= g) a
= f a >>= gList Monad
The List monad is defined as:
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)Left Identity
return a >>= f
= [a] >>= f
= concat (map f [a])
= concat [f a]
= f aRight Identity
xs >>= return
= concat (map return xs)
= concat (map (\x -> [x]) xs)
= concat [[x] | x <- xs]
= xsAssociativity
The proof is more involved, but it relies on the associativity of concat and the distributivity of map over concat.
Why the Monad Laws Matter
The monad laws ensure that:
- Consistency: Monads behave in a consistent, predictable way
- Equational Reasoning: We can reason about monadic code using substitution (Equational Reasoning)
- Refactoring: Code can be refactored without changing behavior
- Composability: Monadic operations can be composed in any order (Monads Basics)
- Do-Notation: The translation of
donotation relies on these laws (Monads Basics)
Consequences of Breaking the Laws
If a monad implementation breaks these laws:
- Code using that monad might behave unexpectedly
- Refactoring could change the behavior of the code
- The
donotation might not work as expected - Composition with other monadic operations might be inconsistent
Practical Implications
For do Notation
The translation of do notation to >>= relies on the monad laws. For example:
do
a <- ma
b <- mb
return (a, b)This translates to:
ma >>= (\a ->
mb >>= (\b ->
return (a, b)))The associativity law ensures that this is equivalent to:
(ma >>= (\a -> mb)) >>= (\b -> return (a, b))For Monad Transformers
Monad transformers combine the effects of multiple monads. The monad laws ensure that these combinations behave reasonably.
For Library Design
When designing a library that uses monads, adhering to the monad laws ensures that users can reason about your library’s behavior and combine it with other monadic code.
Testing the Monad Laws
You can use property-based testing with QuickCheck to verify that your monad implementation satisfies the laws:
import Test.QuickCheck
-- For a custom monad MyMonad
prop_leftIdentity :: (Eq (m b), Monad m) => a -> (a -> m b) -> Bool
prop_leftIdentity a f = (return a >>= f) == f a
prop_rightIdentity :: (Eq (m a), Monad m) => m a -> Bool
prop_rightIdentity m = (m >>= return) == m
prop_associativity :: (Eq (m c), Monad m) => m a -> (a -> m b) -> (b -> m c) -> Bool
prop_associativity m f g = ((m >>= f) >>= g) == (m >>= (\x -> f x >>= g))Key Points to Remember
- The three monad laws are left identity, right identity, and associativity
- These laws ensure consistent and predictable behavior when using monads
- All proper monad implementations must satisfy these laws
- The laws are not enforced by the type system - it’s the developer’s responsibility
- The laws are essential for reasoning about monadic code and for the correct functioning of
donotation