List comprehensions
| The form | |
| Filters | |
| Several generators | |
| let inside a comprehension |
The form
A comprehension describes a list by what its elements look like and where they come from. Read the vertical bar as the words for each:
[x * 2 | x <- [1..5]] -- [2,4,6,8,10]
[toUpper c | c <- "hello"] -- "HELLO"
The left part is an expression in terms of the bound variable, and the right part is a generator x <- list. It is the same idea as map, with more room for conditions.
Filters
Extra conditions after the generator keep only the elements that satisfy them. Several conditions are separated by commas and all must hold:
[x | x <- [1..20], even x] -- [2,4,6,8,10,12,14,16,18,20]
[x | x <- [1..50], even x, x < 10] -- [2,4,6,8]
[(x, y) | x <- [1,2], y <- ["a","b"]]
-- [(1,"a"),(1,"b"),(2,"a"),(2,"b")]
The last example shows that a comprehension can produce tuples, which makes it a compact way to build a small table.
Several generators
Two generators nest like two loops, and the later one may depend on the earlier one. That dependency is the reason to prefer a comprehension over concatMap:
[ (x, y) | x <- [1..3], y <- [x..3] ]
-- [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]
The order matters: the leftmost generator changes slowest, exactly like the outer loop of a nested for.
let inside a comprehension
A let line names an intermediate value for the rest of the comprehension. It cannot filter, only name:
[ scaled | x <- [1..5]
, let scaled = x * x
, scaled > 4 ]
-- [9,16,25]
When a comprehension grows past four lines, rewrite it as a
do block over lists, which lesson 32 covers.
The two are the same construct with different punctuation.
Next:
Recursion replaces loops
Article author: Arthur Isaev