Reading — step 1 of 7
Learn
Foldable and Traversable are the type classes that generalize folding and traversing over data structures. LYAH's later chapters and Real World Haskell's chapter on advanced typeclasses cover them.
Foldable — anything you can fold
The key method:
class Foldable t where
foldr :: (a -> b -> b) -> b -> t a -> b
foldl :: (b -> a -> b) -> b -> t a -> b
-- ... and many more
Lists, Maps, Sets, Trees, Maybe — all Foldable. You can sum, length, elem, minimum, maximum, foldr, foldl on any of them.
import Data.Foldable
sum [1, 2, 3] -- 6
sum (Just 5) -- 5 (Maybe is Foldable!)
sum Nothing -- 0
length [1, 2, 3] -- 3
length (Just 5) -- 1
length Nothing -- 0
elem 3 [1, 2, 3] -- True
elem 3 (Just 3) -- True
Surprising: Maybe is Foldable, with Nothing having length 0 and Just x having length 1.
Common Foldable functions
sum, product :: (Foldable t, Num a) => t a -> a
minimum, maximum :: (Foldable t, Ord a) => t a -> a
length :: Foldable t => t a -> Int
null :: Foldable t => t a -> Bool -- empty?
elem :: (Foldable t, Eq a) => a -> t a -> Bool
any, all :: Foldable t => (a -> Bool) -> t a -> Bool
foldr, foldl :: ...
foldr', foldl' :: strict versions
toList :: Foldable t => t a -> [a] -- collapse to list
Most code uses sum, length, elem, any, all, foldr. Same names work on lists, Maps, Sets — that's the polymorphism win.
Strict folds
import Data.Foldable (foldl')
foldl' (+) 0 [1..1_000_000] -- strict, linear time
foldl (+) 0 [1..1_000_000] -- lazy, builds a thunk pile
Always prefer foldl' over foldl for numeric folds. Lazy foldl builds up unevaluated additions — stack overflow or memory blowup on large inputs. The space leak is so common it's a meme.
Traversable — fold + effect
Like Foldable but threads an EFFECT through the iteration:
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
Reads: "given a function that maps each element to an effectful computation, produce one effect that yields the whole transformed structure."
Practical use
Read N lines, parsing each:
import Text.Read (readMaybe)
parseAll :: [String] -> Maybe [Int]
parseAll = traverse readMaybe
parseAll ["1", "2", "3"] -- Just [1, 2, 3]
parseAll ["1", "abc", "3"] -- Nothing — first failure aborts
traverse runs readMaybe on each, threading the Maybe monad. ANY failure → Nothing; ALL success → Just [results].
IO over a list:
import Data.Traversable
greetAll :: [String] -> IO ()
greetAll = traverse_ (\n -> putStrLn ("Hello, " ++ n))
-- Or for results:
data <- traverse readFile ["a.txt", "b.txt", "c.txt"]
-- IO [String]
traverse returns one big IO action that executes each in sequence.
sequence — the simpler version
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
Flips the structure: [Maybe Int] -> Maybe [Int], [IO String] -> IO [String], etc.
sequence [Just 1, Just 2, Just 3] -- Just [1, 2, 3]
sequence [Just 1, Nothing, Just 3] -- Nothing
sequence [readFile "a", readFile "b"] -- IO [String]
sequence = traverse id. traverse f = sequence . fmap f. Two views of the same operation.
When to use which
- Foldable (sum, length, etc.) — collapsing a structure to a single value
- Traversable (traverse, sequence) — when each element produces an EFFECT (Maybe, Either, IO) and you want one combined effect
- map / fmap — pure transformation, no effects
Common mistakes
- Using non-strict foldl on numbers — space leak. Always foldl' for numeric folds.
- Confusing traverse and mapM — mapM is
traversefor monads (essentially). Modern Haskell uses traverse. - Calling
sumorlengthon Maybe unintentionally — works but might surprise readers. - Sequencing IO actions when you want them in a specific order —
traversepreserves order; if order matters, double-check the type's traversal semantics. - Skipping Foldable/Traversable — basics are simple; learning them unlocks a huge chunk of standard library.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…