What is list comprehension in Haskell?

List comprehension provides a concise method to generate and process lists. This syntactic construct allows us to easily filter, map, or apply conditional logic on the list elements without resorting to explicit recursion. Furthermore, this approach often presents a more readable and understandable alternative to its recursive counterpart.

As Haskell is a purely functional language, list comprehensions enhance code clarity and expressiveness. By avoiding explicit recursion, developers can focus on the "what" rather than the "how," leading to a cleaner, more readable code. This capitalizes on Haskell's strengths by reinforcing the functional programming paradigm.

Syntax

List comprehensions have three main parts:

  • Any number of input lists

  • Any number of predicates (conditions)

  • A single output function

let outputList = [ <outputFunction> | <inputList>, <predicate> ]

Here, the inputList feeds its elements one by one to the outputFunction based on some predicate. A predicate, in this context, is simply a function that examines an item and returns either true (if the item meets the condition) or false (if the item fails to meet the condition). The outputFunction generates the resulting list. This generates a new outputList leaving the inputList unchanged.

This syntax will be easier to understand as you go through some of real-world use cases of list comprehension listed below.

Use cases of list comprehension

Let's look at some use cases of list comprehension in Haskell:

Generating a list of integers based on another list

Generating a list based on another list is a common use case, especially in the field of data science. Suppose we have an input list of prices for goods and we have to apply a 10% discount on all those prices. We can accomplish that with list comprehension as below:

main :: IO ()
main = do
let numbers = [x * 0.9 | x <- [1..10]]
print numbers

Explanation

  • Lines 1–2: Defining the main entry point.

  • Line 3: This comprehension iterates over a list of integers from 1 to 10 (representing prices). For each integer x in the range, it multiplies that integer with 0.9 before including it in the resultant list to get the discounted prices.

  • Line 4: Printing the resultant list.

Filtering with a predicate

Filtering with a predicate means selecting items from a collection based on a specific test or condition. Essentially, the predicate serves as a filter, determining which items make the cut.

One real-world example of filtering is selecting all even number out of a list. Let's take a look at how we can implement that below:

main :: IO ()
main = do
let evenNumbers = [x | x <- [1..10], even x]
print evenNumbers

Explanation

  • Lines 1–2: Define the main entry point.

  • Line 3: This comprehension iterates over a list of integers from 1 to 10. This time our predicate even x checks if the each integer x is even. The output function here simply includes all the integers that pass the condition in the predicate. Thus the resultant list will only include even numbers from 1 to 10.

  • Line 4: Print the resultant list.

Making combinations from multiple inputs

In the context of list comprehension, multiple inputs refers to drawing values from more than one source or list to generate the desired output list. By iterating over multiple lists simultaneously, we can create combinations or perform operations using elements from all involved lists.

One real-world example for combinations is of pairing students with available advisors for a study program. Given a list of students ["Fatima", "Kazuya"] and a list of advisors ["Dr. Newton", "Dr. Gomez"], using list comprehension with multiple inputs, we can easily generate all possible student-advisor pairings as below:

main :: IO ()
main = do
let pairs = [(x, y) | x <- ["Fatima", "Kazuya"], y <- ["Dr. Newton", "Dr. Gomez"]]
print pairs

Explanation

  • Lines 1–2: Define the main entry point.

  • Line 3: This comprehension operates on two input lists. For every student x from the first list and each advisor y from the second list, the output function produces a tuple (x, y). Thus, the resultant list includes all possible unique pairs from the two input lists.

  • Line 4: Print the resultant list.

Filtering with multiple predicates

In the context of list comprehension, multiple predicates refers to applying more than one condition or filter to the elements being processed. Each predicate tests the elements, and only those that satisfy all conditions make it to the final list.

One real-world example of filtering with multiple predicates is selecting meals based on their prices and calories. Suppose we only want meals that cost less than $4, and have more than 300 calories. Using list comprehension with multiple predicates, you could achieve this as follows:

main :: IO ()
main = do
let meals = [("Burger", 3, 310), ("Pizza", 5, 340), ("Ramen", 2, 250)]
let filteredMeals = [name | (name, price, calories) <- meals, price < 4, calories > 300]
print filteredMeals

Explanation

  • Lines 1–2: Define the main entry point.

  • Line 3: This comprehension iterates over a list of tuples that contain information for a meal (namely, the name of the food, price of the food, and total calories). This time we have two predicates. The first predicate checks if each meal costs less than $4 and the second predicate checks if each meal contains more than 300 calories. Only meals satisfying both conditions will be included in the resultant list (which in this case only includes "Burger").

  • Line 4: Print the resultant list.

Conclusion

In Haskell, list comprehension offers a powerful and elegant way to define and manipulate lists. By providing a more declarative syntax, it enhances code readability and eliminates the necessity for explicit recursion in many scenarios. Thus, it is a perfect example of Haskell's philosophy of clear, concise, and functional programming.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved