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 x

Use 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 Nothing

List 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

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 r

Use 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) seed

Either 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 a

Use 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 x

Use 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

  1. Maybe represents computations that might fail
  2. List represents non-deterministic computations with multiple results
  3. Reader provides read-only access to shared environment
  4. Writer allows accumulating a secondary value (like a log)
  5. State enables maintaining and modifying state throughout a computation
  6. Either is like Maybe but with detailed error information
  7. IO handles side effects and interaction with the external world
  8. Identity is the simplest monad with no additional effects