Haskell Lazy Evaluation Guide — Thunks, Strictness, and Performance
In this tutorial, you will learn about Haskell Lazy Evaluation Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Haskell lazy evaluation (non-strict) wraps expressions in thunks that are evaluated only when their value is demanded -- with seq for selective strictness, BangPatterns for strict fields, and foldl' for space-efficient reduction.
How Laziness Works
-- Expression is not evaluated until needed
let x = 1 + 2 * 3 -- thunk created
let y = x + 5 -- another thunk
print y -- both thunks evaluated now
-- Infinite lists work because of laziness
ones = 1 : ones
take 5 ones -- [1,1,1,1,1] (only 5 elements created)
seq and Strict Evaluation
-- seq forces evaluation of first argument, returns second
-- seq a b evaluates a to WHNF, then returns b
strictSum :: Int -> Int -> Int
strictSum x y = x `seq` y `seq` x + y
-- Using $!
-- ($!) is strict application
f x = x * 2
f $! 3 + 4 -- evaluates 3+4 before calling f
BangPatterns
{-# LANGUAGE BangPatterns #-}
-- Strict function arguments
sumStrict :: [Int] -> Int
sumStrict = go 0
where
go !acc [] = acc
go !acc (x:xs) = go (acc + x) xs
-- Strict record fields
data Point = Point
{ x :: !Double -- always strict
, y :: !Double
}
-- Without bang, fields are lazy
data LazyPoint = LazyPoint
{ lx :: Double -- may be thunk
, ly :: Double
}
Space Leaks
-- foldl causes space leak (builds thunks)
badSum :: [Int] -> Int
badSum = foldl (+) 0
-- Thunks: ((((0+1)+2)+3)+4) builds up
-- foldl' is strict (no thunks)
import Data.List (foldl')
goodSum :: [Int] -> Int
goodSum = foldl' (+) 0
-- Evaluates: 1 -> 3 -> 6 -> 10
-- Difference:
-- For [1..1000000], foldl overflows stack
-- foldl' runs in constant space
Common Mistakes
1. Space leaks with foldl
Always use foldl' from Data.List for strict accumulation. foldl causes thunk buildup on large lists.
2. Overusing seq
Adding seq everywhere doesn't help. Use it only where laziness causes space leaks or unexpected thunk retention.
3. Infinite loops in strict contexts
let x = 1:x works lazily. But print $ length x (strict) will loop forever. Be careful with infinite structures.
Practice Questions
1. What is a thunk? A deferred computation -- an expression that hasn't been evaluated yet. Lazy evaluation creates thunks.
2. What does seq do?
seq a b evaluates a to Weak Head Normal Form, then returns b. Used to force evaluation at specific points.
3. When should you use foldl' instead of foldl? Always. foldl' (strict) runs in constant space. foldl (lazy) builds thunks that overflow the stack.
FAQ
{{< faq question="What is Weak Head Normal Form (WHNF)?" >}} An expression evaluated to its outermost constructor or lambda. Not all subexpressions are evaluated. seq evaluates to WHNF. {{< /faq >}}
{{< faq question="Can I opt out of laziness?" >}} Yes, partially. Use seq, BangPatterns, strict data types, or the Strict extension for module-wide strictness. {{< /faq >}}
{{< faq question="How does laziness help performance?" >}} It avoids unnecessary computation, enables modular code (separate generator from consumer), and allows infinite data structures. {{< /faq >}}
What's Next
Now learn about error handling.
| Topic | Description | Link |
|---|---|---|
| Error Handling | Exception Handling | {{< ref "18-error-handling" >}} |
| Testing | Testing Haskell code | {{< ref "19-tests" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro