CodeToLive

Monads & IO in Haskell

Monads are a powerful abstraction for sequencing computations in Haskell. They provide a way to handle side effects, manage state, and compose operations in a pure functional language.

The Problem with Purity

Haskell is a pure language, meaning functions can't have side effects. This creates a challenge for I/O operations. Monads provide a solution by sequencing operations in a controlled way.


-- This wouldn't work in pure Haskell
-- main = print (readLine())  -- Can't do this!

-- Instead, we use IO monad
main :: IO ()
main = do
  input <- getLine
  putStrLn ("You said: " ++ input)
      

The Monad Typeclass

The Monad typeclass defines two main operations:


class Monad m where
  (>>=)  :: m a -> (a -> m b) -> m b  -- bind
  return :: a -> m a

  -- Derived operations
  (>>)   :: m a -> m b -> m b
  fail   :: String -> m a
      

Maybe Monad

The Maybe monad handles computations that might fail:


-- Maybe definition
data Maybe a = Nothing | Just a

instance Monad Maybe where
  return x = Just x
  Nothing >>= _ = Nothing
  Just x >>= f = f x

-- Example usage
safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)

calculate :: Double -> Double -> Maybe Double
calculate x y = do
  a <- safeDivide x y
  b <- safeDivide (a + 10) y
  return (b * 2)
      

Either Monad

Either is similar to Maybe but can carry error information:


data Either a b = Left a | Right b

instance Monad (Either e) where
  return = Right
  Left e >>= _ = Left e
  Right x >>= f = f x

-- Example usage
safeDivide :: Double -> Double -> Either String Double
safeDivide _ 0 = Left "Division by zero"
safeDivide x y = Right (x / y)

calculate :: Double -> Double -> Either String Double
calculate x y = do
  a <- safeDivide x y
  b <- safeDivide (a + 10) y
  return (b * 2)
      

List Monad

The List monad models nondeterministic computations:


instance Monad [] where
  return x = [x]
  xs >>= f = concat (map f xs)

-- Example usage
pairs :: [Int] -> [Int] -> [(Int, Int)]
pairs xs ys = do
  x <- xs
  y <- ys
  return (x, y)

-- Equivalent to list comprehension:
-- pairs xs ys = [(x, y) | x <- xs, y <- ys]
      

IO Monad

The IO monad handles input/output operations:


-- Simple IO actions
main :: IO ()
main = do
  putStrLn "Enter your name:"
  name <- getLine
  putStrLn ("Hello, " ++ name ++ "!")

-- Sequencing IO actions
greet :: IO ()
greet = 
  putStr "Hello " >> 
  putStr "world!" >> 
  putStrLn ""

-- Reading and writing files
copyFile :: FilePath -> FilePath -> IO ()
copyFile src dest = do
  contents <- readFile src
  writeFile dest contents
      

do Notation

The do notation provides imperative-style syntax for monadic code:


-- Without do notation
main :: IO ()
main = 
  putStrLn "Enter a number:" >> 
  getLine >>= \input -> 
  let n = read input :: Int in
  putStrLn ("The square is: " ++ show (n * n))

-- With do notation
main :: IO ()
main = do
  putStrLn "Enter a number:"
  input <- getLine
  let n = read input :: Int
  putStrLn ("The square is: " ++ show (n * n))
      

Monad Laws

All monads must satisfy three laws:


-- Left identity
return x >>= f ≡ f x

-- Right identity
m >>= return ≡ m

-- Associativity
(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
      

Common Monad Functions

Useful functions for working with monads:


-- Lift a function into a monad
liftM :: Monad m => (a -> b) -> m a -> m b
liftM f m = m >>= \x -> return (f x)

-- Sequence monadic actions
sequence :: Monad m => [m a] -> m [a]
sequence [] = return []
sequence (m:ms) = do
  x <- m
  xs <- sequence ms
  return (x:xs)

-- Map over a list with a monadic function
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f xs = sequence (map f xs)

-- Filter with a monadic predicate
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
filterM _ [] = return []
filterM p (x:xs) = do
  b <- p x
  ys <- filterM p xs
  return (if b then x:ys else ys)
      

Creating Custom Monads

You can define your own monads for specific purposes:


-- State monad example
newtype State s a = State { runState :: s -> (a, s) }

instance Monad (State s) where
  return x = State $ \s -> (x, s)
  m >>= f = State $ \s -> 
    let (a, s') = runState m s
    in runState (f a) s'

-- Reader monad example
newtype Reader r a = Reader { runReader :: r -> a }

instance Monad (Reader r) where
  return x = Reader $ \_ -> x
  m >>= f = Reader $ \r -> 
    let a = runReader m r
    in runReader (f a) r
      
← Previous: Higher-Order Functions | Next: Lazy Evaluation →