Lesson 6 / 25
Recursion and Higher-Order Functions
Replace loops with recursion, map, filter and folds, and compose functions.
Loops without loops
Without mutable variables, repetition is expressed with recursion: a base case and a recursive case that works on a smaller input. Most recursion follows common shapes captured by higher-order functions: map transforms each element, filter keeps matching ones, and folds combine a structure into one value. foldr f z replaces each : with f and [] with z; it works on infinite lists when f is lazy in its second argument and is the natural choice for building lists. foldl' (from Data.List) is a strict left fold, the right choice for sums and counters on large lists, because the lazy foldl builds up a chain of unevaluated thunks and can exhaust memory. Functions compose with . ((f . g) x = f (g x)) and $ applies a function with the lowest precedence, avoiding parentheses: print $ sum $ map square xs. Writing functions without naming their arguments, such as countPaid = length . filter paid, is called point-free style; use it when it reads clearly, not to be clever.
Recursion, folds and composition
The same sums written three ways, plus composition.
import Data.List (foldl')
import Data.Char (toUpper, isAlpha)
-- explicit recursion
sumList :: [Int] -> Int
sumList [] = 0
sumList (x : xs) = x + sumList xs
-- strict left fold: constant stack, good for long lists
sumStrict :: [Int] -> Int
sumStrict = foldl' (+) 0
-- our own map, written with foldr
myMap :: (a -> b) -> [a] -> [b]
myMap f = foldr (\x acc -> f x : acc) []
data Order = Order { orderId :: String, totalPaise :: Int, paid :: Bool }
paidRevenue :: [Order] -> Int
paidRevenue = sum . map totalPaise . filter paid -- point-free pipeline
shout :: String -> String
shout = map toUpper . filter isAlpha
main :: IO ()
main = do
print (sumList [1 .. 100]) -- 5050
print (sumStrict [1 .. 10000000]) -- 50000005000000
print (myMap (* 2) [1, 2, 3 :: Int]) -- [2,4,6]
print $ paidRevenue [Order "o1" 120000 True, Order "o2" 30000 False] -- 120000
putStrLn (shout "hello, world!") -- HELLOWORLDUse foldl' for accumulation
The lazy foldl is almost never what you want: summing ten million numbers with it can build millions of thunks. Import foldl' from Data.List for strict accumulation, or use sum, which is strict in modern GHC versions.
Quick check: Why is foldl' usually preferred over foldl for summing a large list?
- It evaluates the accumulator at each step, avoiding a build-up of unevaluated thunks
- It is lazier
- It works only on infinite lists
- It sorts the list first
Answer
It evaluates the accumulator at each step, avoiding a build-up of unevaluated thunks — Strict accumulation keeps memory use constant.