Haskell List Comprehensions
Introduction to List Comprehensions
Welcome to Unit 5 of our course! In the previous lessons, you learned how to work with lists and how to group different pieces of data using tuples. As you continue writing Haskell programs, you will often find that you need to create a new list based on the contents of an existing one.
In this lesson, we will introduce a powerful concept called a list comprehension. If you have ever seen set-builder notation in a mathematics class, this idea will feel very familiar. A list comprehension provides a declarative way to create lists. This means that instead of writing out step-by-step instructions on exactly how to build the list, you simply describe what you want the final list to look like. It is a clean, readable syntax that allows you to transform and filter data seamlessly.
Transforming Data with Generators
A list comprehension is enclosed in square brackets [ ] and is split into two main parts by a vertical pipe |. The right side of the pipe defines where your data comes from, and the left side defines what the output will look like.
Let us start with the right side, which is known as a generator. Haskell also supports range syntax: [1 .. 5] creates the list [1,2,3,4,5]. We can draw elements from this list one by one using a left-pointing arrow <-.
In the snippet above, n <- [1 .. 5] is the generator. You can read it as: "Draw elements from the list [1 .. 5] one by one, and name each element n." The left side of the pipe is simply n, meaning our output is exactly what we drew from the source list.
However, the real power of list comprehensions comes from modifying that output. Let us say we want a list of squared numbers instead. We can change the output expression on the left side to multiply n by itself.
By changing the expression to n * n, we are instructing Haskell to compute the square for each element drawn by the generator. If you were to print the squares list, the output would be:
Filtering Data with Guards
Often, you do not want to transform every element from your source list. Instead, you might only be interested in the elements that meet a specific condition. In a list comprehension, you can achieve this by adding a guard. A guard is a simple true-or-false test placed at the end of the comprehension, separated from the generator by a comma.
Let us start by simply drawing numbers from a slightly larger list — from 1 to 10.
Now, suppose we want to filter this list so that we only keep the even numbers. We can use the built-in Haskell function even, which evaluates to true if a number is even and false otherwise. We add this guard right after our generator.
When Haskell evaluates this comprehension, it draws a number n from the list [1 .. 10]. Then, it checks the guard even n. If the guard is true, the number is passed to the output expression (which is just n in this case) and added to the new list. If the guard is false, the number is skipped.
If you print the evens list, the output will be:
