Sometimes, the best way to learn a language is by using a simple, even slightly “weird” goal as an excuse to explore
its core concepts. Here, the goal is to extract the sides (apart from the hypotenuse) from a list of
Pythagorean triples—but really, it’s just a reason to learn how pattern matching, recursion, and
let blocks work in Haskell.
In Haskell, you can deconstruct tuples directly in function arguments. It’s an elegant and expressive way to “take apart” data structures. Let’s define three simple functions to get each element from a 3-tuple:
myFst :: (Int, Int, Int) -> Int
myFst (x, _, _) = x
mySnd :: (Int, Int, Int) -> Int
mySnd (_, x, _) = x
myThrd :: (Int, Int, Int) -> Int
myThrd (_, _, x) = x
The underscores (_) mean “I don’t care about this value.” Only the variable names you use (like
x) are bound.
Let’s say we have a list of triples representing (a, b, c) values (like Pythagorean triples). We want to extract
two lists: one with the first element of each triple, and one with the second.
We can do this recursively:
myExtractor :: [(Int, Int, Int)] -> ([Int], [Int])
myExtractor [] = ([], [])
myExtractor (x:xs) = ((myFst x):xs1, (mySnd x):xs2)
where (xs1, xs2) = myExtractor xs Let’s break this down:
(x:xs) matches a non-empty list: x is the head, and xs is the tail.[] returns two empty lists.myExtractor xs gives you two partial results, bound in the
where clause as (xs1, xs2).x to each list using :.This pattern—matching the head/tail, then combining recursive results—is the backbone of functional recursion in Haskell.
let blocks
The where clause is great for binding helper values, but sometimes you need more control over the scope.
That’s where let and in come in. Here’s a version of myExtractor that uses them:
myExtractor2 :: [(Int, Int, Int)] -> ([Int], [Int])
myExtractor2 [] = ([], [])
myExtractor2 (x:xs) =
let (xs1, xs2) = myExtractor xs
a = 3
in (((myFst x) + a):xs1, (mySnd x):xs2)
This version does the same thing, but introduces a new local variable a (which we arbitrarily add to
myFst x) to illustrate that you can define intermediate values with let. The syntax is:
let bindings in expression
Everything defined in the let section is available inside the in expression, but not outside.
let blocks give you local binding power—great for clarity and immutability.
Think of pattern matching as “cutting open” your data, recursion as “peeling through the list one layer at a time,”
and let as “taking notes along the way.” Each tool builds a different piece of Haskell’s elegant simplicity.
Our little “extract the sides” exercise might seem arbitrary, but it’s a perfect excuse to explore three of Haskell’s
most important ideas. Once you’re comfortable with these, you’ll find yourself using them everywhere—pattern matching
for data access, recursion for iteration, and let or where for organizing logic cleanly.