This page explores several common monads in Haskell and their practical applications. For a general introduction, see Monads Basics. For laws and properties, see Monad Laws. For combining monads, see Monad Transformers.
Maybe Monad
The Maybe monad represents computations that might fail. See also: Error Handling, Algebraic Data Types.
Definition
data Maybe a = Nothing | Just a
instance Monad Maybe where
return = Just
Nothing >>= _ = Nothing
(Just x) >>= f = f xUse Cases
- Representing partial functions (those not defined for all inputs)
- Error handling without exceptions (Error Handling)
- Optional values
Example
-- Safe lookup in a list
safeLookup :: Int -> [a] -> Maybe a
safeLookup _ [] = Nothing
safeLookup 0 (x:_) = Just x
safeLookup n (_:xs) = safeLookup (n-1) xs
-- Chain computations with potential failures
lookupAndProcess :: [Int] -> Int -> Maybe Int
lookupAndProcess list idx = do
value <- safeLookup idx list
if value > 0
then Just (value * 2)
else NothingList Monad
The List monad represents non-deterministic computations with multiple possible results. See also: Lists and List Comprehensions, Pattern Matching and Recursion.
Definition
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)Use Cases
- Generating all possible outcomes (Lists and List Comprehensions)
- Backtracking algorithms
- Combinatorial problems
Example
-- Generate all possible dice rolls
diceRolls :: Int -> [Int]
diceRolls n = do
-- Roll n dice
rolls <- replicateM n [1..6]
-- Return the sum
return (sum rolls)
-- All pythagorean triples with components less than n
pythagoreanTriples :: Int -> [(Int, Int, Int)]
pythagoreanTriples n = do
a <- [1..n]
b <- [a..n] -- Ensure b >= a
c <- [b..n] -- Ensure c >= b
guard (a*a + b*b == c*c) -- Only keep results that satisfy the equation
return (a, b, c)Reader Monad
The Reader monad represents computations that can read values from a shared environment. See also: Higher-Order Functions for the use of functions as first-class values.
Definition
newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return x = Reader (\_ -> x)
(Reader f) >>= g = Reader $ \r ->
let a = f r
Reader h = g a
in h rUse Cases
- Dependency injection
- Configuration management
- Functions sharing an immutable context
Example
import Control.Monad.Reader
-- Configuration data
data Config = Config {
baseUrl :: String,
timeout :: Int,
maxRetries :: Int
}
-- Functions using configuration
getResource :: String -> Reader Config String
getResource path = do
config <- ask -- Get the environment
return $ baseUrl config ++ "/" ++ path
fetchWithRetry :: String -> Reader Config String
fetchWithRetry resource = do
path <- getResource resource
config <- ask
return $ "Fetching " ++ path ++
" with timeout " ++ show (timeout config) ++
" and " ++ show (maxRetries config) ++ " retries"
-- Main program using these functions
program :: Reader Config String
program = do
result1 <- getResource "users"
result2 <- fetchWithRetry "data"
return (result1 ++ "\n" ++ result2)
-- Run the program with a specific config
runProgram :: String
runProgram = runReader program (Config "https://api.example.com" 1000 3)Writer Monad
The Writer monad represents computations that can produce a secondary stream of data (e.g., a log). See also: Pattern Matching and Recursion for recursive logging examples.
Definition
newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where
return a = Writer (a, mempty)
(Writer (a, w)) >>= f =
let (b, w') = runWriter (f a)
in Writer (b, w `mappend` w')Use Cases
- Logging
- Collecting statistics
- Building up strings or other monoid values
Example
import Control.Monad.Writer
-- Log messages during computation
factorial :: Int -> Writer [String] Int
factorial n = do
tell ["Computing factorial of " ++ show n]
if n <= 1
then do
tell ["Factorial of " ++ show n ++ " is 1"]
return 1
else do
res <- factorial (n-1)
let result = n * res
tell ["Factorial of " ++ show n ++ " is " ++ show result]
return result
-- Run the computation and get result with logs
computeFactorial :: Int -> (Int, [String])
computeFactorial n = runWriter (factorial n)State Monad
The State monad represents computations that can maintain and modify state. See also: Pattern Matching and Recursion for recursive stateful algorithms.
Definition
newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return a = State $ \s -> (a, s)
(State h) >>= f = State $ \s ->
let (a, s') = h s
State g = f a
in g s'Use Cases
- Stateful algorithms
- Passing mutable state through a computation
- Random number generation
Example
import Control.Monad.State
-- Simple random number generator using state
type RandomState = State Int
-- Linear congruential generator parameters
a, c, m :: Int
a = 1103515245
c = 12345
m = 2^31
-- Generate a random number and update the seed
nextRandom :: RandomState Int
nextRandom = do
seed <- get
let newSeed = (a * seed + c) `mod` m
put newSeed
return newSeed
-- Generate n random numbers
randomList :: Int -> RandomState [Int]
randomList n = replicateM n nextRandom
-- Run with an initial seed
generateRandoms :: Int -> Int -> [Int]
generateRandoms seed count = evalState (randomList count) seedEither Monad
The Either monad represents computations that might fail with an error value.
Definition
data Either e a = Left e | Right a
instance Monad (Either e) where
return = Right
Left e >>= _ = Left e
Right a >>= f = f aUse Cases
- Error handling with specific error information
- Validation with detailed error messages
- Railway-oriented programming
Example
import Control.Monad.Trans.Either
-- Error types
data ValidationError =
EmptyField String
| InvalidFormat String
| OutOfRange String Int Int Int
deriving Show
-- Validation functions
validateName :: String -> Either ValidationError String
validateName "" = Left (EmptyField "Name")
validateName name = Right name
validateAge :: Int -> Either ValidationError Int
validateAge age
| age < 0 = Left (OutOfRange "Age" age 0 120)
| age > 120 = Left (OutOfRange "Age" age 0 120)
| otherwise = Right age
-- Combined validation
validatePerson :: String -> Int -> Either ValidationError (String, Int)
validatePerson name age = do
validName <- validateName name
validAge <- validateAge age
return (validName, validAge)IO Monad
The IO monad represents computations that perform input/output operations.
Definition
The implementation is built into the Haskell runtime system.
Use Cases
- Reading/writing files
- Network operations
- Console input/output
- Any interaction with the external world
Example
-- Interactive program
interactiveCalculator :: IO ()
interactiveCalculator = do
putStrLn "Enter the first number:"
input1 <- getLine
putStrLn "Enter the second number:"
input2 <- getLine
putStrLn "Enter operation (+, -, *, /):"
op <- getLine
let num1 = read input1 :: Double
num2 = read input2 :: Double
result = case op of
"+" -> num1 + num2
"-" -> num1 - num2
"*" -> num1 * num2
"/" -> num1 / num2
_ -> error "Invalid operation"
putStrLn $ "Result: " ++ show result
putStrLn "Continue? (y/n)"
cont <- getLine
if cont == "y"
then interactiveCalculator
else putStrLn "Goodbye!"Identity Monad
The Identity monad is the simplest monad, with no additional effects.
Definition
newtype Identity a = Identity { runIdentity :: a }
instance Monad Identity where
return = Identity
Identity x >>= f = f xUse Cases
- Understanding monadic concepts
- Base case for monad transformers
- Pure computations requiring a monadic interface
Example
import Control.Monad.Identity
-- Pure computation in a monadic style
factorial :: Int -> Identity Int
factorial n = do
if n <= 1
then return 1
else do
res <- factorial (n-1)
return (n * res)
-- Run the computation
computeFactorial :: Int -> Int
computeFactorial n = runIdentity (factorial n)Combining Monads
Different monads solve different problems, but we often need to combine their capabilities. This is where monad transformers come in (covered in a separate page).
Key Points to Remember
Mayberepresents computations that might failListrepresents non-deterministic computations with multiple resultsReaderprovides read-only access to shared environmentWriterallows accumulating a secondary value (like a log)Stateenables maintaining and modifying state throughout a computationEitheris likeMaybebut with detailed error informationIOhandles side effects and interaction with the external worldIdentityis the simplest monad with no additional effects