CodeToLive

Lazy Evaluation in Haskell

Haskell uses lazy evaluation by default, meaning expressions are not evaluated until their results are actually needed. This enables powerful programming techniques and optimizations.

What is Lazy Evaluation?

In lazy evaluation:

Benefits of Laziness

Lazy evaluation provides several advantages:


-- Infinite lists
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

-- Take the first 10 Fibonacci numbers
first10Fibs = take 10 fibs  -- [0,1,1,2,3,5,8,13,21,34]

-- Only computes what's needed
sumFirst10 = sum first10Fibs  -- Doesn't compute the whole infinite list
      

Thunks

Unevaluated expressions are stored as "thunks":


-- Thunk example
x = 2 + 3  -- Not evaluated yet
y = x * x   -- Another thunk

-- When we actually need the value (e.g., print y)
-- Then x is evaluated to 5, and y to 25
      

Strict vs Non-strict

Haskell is non-strict, meaning function arguments aren't evaluated before being passed:


-- Non-strict function
const :: a -> b -> a
const x _ = x  -- Second argument is never evaluated

-- Example usage
result = const 42 (error "This won't be evaluated")
-- result is 42, no error occurs
      

Pattern Matching Forces Evaluation

Pattern matching causes evaluation to occur:


-- Forces evaluation of first element
firstElement :: [a] -> a
firstElement (x:_) = x

-- This would force evaluation of the error
-- bad = firstElement [error "Boom!", 2, 3]
      

Controlling Evaluation

You can control evaluation with strictness annotations:


-- Bang patterns (force evaluation)
{-# LANGUAGE BangPatterns #-}

strictAdd :: Int -> Int -> Int
strictAdd !x !y = x + y  -- x and y are evaluated immediately

-- seq function
strictPair :: a -> b -> (a, b)
strictPair x y = x `seq` y `seq` (x, y)
      

Infinite Data Structures

Laziness enables working with infinite structures:


-- Infinite list of ones
ones :: [Int]
ones = 1 : ones

-- Infinite list of natural numbers
nats :: [Int]
nats = [0..]

-- Sieve of Eratosthenes (infinite primes)
primes :: [Int]
primes = sieve [2..]
  where
    sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
      

Lazy IO

Laziness can be used with IO for efficient processing:


-- Read file lazily
processLargeFile :: FilePath -> IO ()
processLargeFile path = do
  contents <- readFile path  -- Lazy read
  let linesList = lines contents
  mapM_ processLine (take 100 linesList)  -- Only read first 100 lines

processLine :: String -> IO ()
processLine line = putStrLn ("Processing: " ++ line)
      

Space Leaks

Laziness can sometimes cause memory issues:


-- Bad: builds up thunks
sumSquares :: [Int] -> Int
sumSquares = foldr (\x acc -> x * x + acc) 0

-- Better: strict accumulator
sumSquares' :: [Int] -> Int
sumSquares' = foldl' (\acc x -> acc + x * x) 0
      

Strict Data Types

You can define strict fields in data types:


-- Strict data type
data StrictPair a b = StrictPair !a !b

-- Evaluation happens when constructing
pair = StrictPair (1 + 2) (3 + 4)  -- Both additions happen immediately
      

Deepseq for Forcing Evaluation

The deepseq package can force complete evaluation:


import Control.DeepSeq

-- Force complete evaluation
forceEvaluation :: NFData a => a -> a
forceEvaluation x = x `deepseq` x

-- Example usage
main = do
  let bigList = [1..1000000] :: [Int]
  print (forceEvaluation (sum bigList))
      
← Previous: Monads & IO | Next: Concurrent Haskell →