If you’ve (ever) took a “course” in maths – you’ve (probably) “run” into «set comprehensions»… They’re (normally) used for building more specific sets (out – of general sets)… A basic comprehension example (for a set, which contains the first ten even natural numbers) is: S = { 2*x | x is N <= 10 }
. The part before the “pipe” – is called the “output” function; x
– is the “variable”; N
– is the “input” set; and x <= 10
– is the “predicate”. That – means that: the set – contains the doubles of all natural numbers, which are specified to accord the predicate.
If we wanted to write that in “Haskell” – we could do something like this: take 10 [2, 4 ..]
.
But what – if we didn’t want doubles of the first 10
natural numbers, but some kind of a more complex function (applied on them)? … We – could use a «list comprehension» (for that).
«List comprehensions» – are very similar to «set comprehensions»… We’ll stick – to getting the first 10
even numbers (for now). The list comprehension we could use – is: [ x*2 | x <- [1 .. 10] ]
. The x
– is drawn from [1 .. 10]
, and (for every element in [1 .. 10]
, which – we have bound: to x
) – we’ll get that element: doubled… Here’s – that comprehension (in “action”):
ghci> [x*2 | x <- [1..10]]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
As you can see, – we – get the desired results! …
Now – let’s add a condition (or – a predicate), to that “comprehension”… Predicates – go after the binding parts, – and – are separated (from them) by a “comma”. Let’s say: we – want (only) the elements, which (when doubled) are greater than (or equal to) 12
:
ghci> [x*2 | x <- [1 .. 10], x*2 >= 12]
[12, 14, 16, 18, 20]
Cool? It works?
How about – if we wanted all numbers (from 50
– to 100
), whose remainder (when – divided with a 7
) is a 3
? … Easy:
ghci> [ x | x <- [50 .. 100], x `mod` 7 == 3]
[52, 59, 66, 73, 80, 87, 94]
Success? …
Note that: weeding out lists (by predicates) – is also called “filtering”.
We – took a list (of numbers); – and – filtered it (by the predicate)… Now – for another example.
Let’s say: we – want a comprehension (which – replaces: each odd number (greater, than a 10
) – with a BANG!
, and, each odd number (which’s – less, than 10
) – with a BOOM!
)… If a number isn’t odd – we’ll throw it out (of our list). For a convenience: we’ll – put that (comprehension) inside a function; so – we can (easily) re-use it:
boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x ]
The last part (of – the comprehension) – is – the predicate… The function odd
– returns True
on odd numbers (and – False
– on even ones). The element – will be included to the list (only), if all predicates evaluate to True
.
ghci> boomBangs [7 .. 13]
["BOOM!", "BOOM!", "BANG!", "BANG!"]
We – can include several predicates! … If we wanted all numbers: from 10
to 20
, which are not 13
, 15
, 19
, – we’d – do:
ghci> [ x | x <- [10 .. 20], x /= 13, x /= 15, x /= 19]
[10, 11, 12, 14, 16, 17, 18, 20]
Not only can we have multiple predicates (in list comprehensions) (an element – must accord all predicates, to be included into resulting list): we can (also) draw from several lists. When drawing from several lists – comprehensions produce all combinations (of given lists); and then – joins them (by – the output function, which we had supplied)… A list, produced by a comprehension, which draws from two lists of length 4
, – will have a length of 16
(provided – we don’t filter them).
If we have two lists (a [2, 5, 10]
and a [8, 10, 11]
), and we want to get the products (of – all the possible combinations of numbers, from those lists) – here’s what we’d do:
ghci> [ x*y | x <- [2, 5, 10], y <- [8, 10, 11] ]
[16, 20, 22, 40, 50, 55, 80, 100, 110]
As expected: the length (of the new list) – is 9
.
What – if we wanted all possible products, – which – are bigger, than 50
?
ghci> [ x*y | x <- [2, 5, 10], y <- [8, 10, 11], x*y > 50 ]
[55, 80, 100, 110]
Adjectives + nouns:
ghci> let nouns = ["hobo", "frog", "pope"]
let adjectives = ["lazy", "grouchy", "scheming"]
ghci> [adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
["lazy hobo", "lazy frog", "lazy pope",
"grouchy hobo", "grouchy frog", "grouchy pope",
"scheming hobo", "scheming frog", "scheming pope"]
Re-written “length”: sums elements as 1
(_
– means no name):
length' xs = sum [1 | _ <- xs]
Strings – are lists; predicate – “upper” case (A–Z) characters:
removeNonUppercase st = [ c | c <- st, c `elem` ['A' .. 'Z']]
removeNonUppercase "Hahaha! Ahahaha!" → "HA"
removeNonUppercase "IdontLIKEFROGS" → "ILIKEFROGS"
Comprehension (lists in lists) – to have even numerics:
ghci> let xxs = [[1, 3, 5, 2, 3, 1, 2, 4, 5], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 4, 2, 1, 6, 3, 1, 3, 2, 3, 6]]
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2, 2, 4], [2, 4, 6, 8], [2, 4, 2, 6, 2, 6]]
It’s better – to split comprehensions (across lines), if they are nested.