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.