NML

List comprehensions are awesome

By

I’m a C# programmer, so I’ll be the first to admit that the functional programming world makes my head spin, but I find it very intriguing.

So I started investigating Haskell, a purely functional language (using a free book on this website: LearnYouAHaskell) and I was really impressed with Haskell’s list comprehensions. Even if you never use Haskell in your life, I think it’s still worth learning about some of its features. It broadens your thinking and shows that there is more to life than semicolons and curly braces.

Here are some list features and list comprehensions in Haskell. I’ll start simple and build from there. Let’s see what Haskell can do…

Haskell allows you to create a range as follows:

haskell> [1..10]

Which produces

[1,2,3,4,5,6,7,8,9,10]

Ranges can also be constructed as follows, by specifying two items to start with, indicating the step. For example:

haskell> [5,10..100]

Produces:

[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]

Finally, you can also create an infinite list, which will continue evaluating until you no longer need values (i.e. breaking out of a loop). For example:

haskell>[1,5..]

Produces:

[1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,193,197,201,205,209,213,217,221,225,229,233,237,241,245,249,253,257,261,265,269,273,277,281,285,289,293,297,301,305,309,313,317,321,325,329,333,337,341,345... etc... etc...]

Now that I’ve shown how to declare ranges, let’s briefly look at methods that interact with lists. “Head” takes the first item from a list.

haskell> head [1,3..30]
1

“Tail” returns all the items except the “head”.

haskell> tail [1,3..30]
[3,5,7,9,11,13,15,17,19,21,23,25,27,29]

“Take” takes a specified number of items from the list.

haskell> take 3[1,3..30]
[1,3,5]

Haskell also has a “cycle” method that cycles through the specified list infinitely. The following example takes the first 10 items from the list that cycles through 1,3,4:

haskell>take 10 (cycle[1,3,4])
[1,3,4,1,3,4,1,3,4,1]
Now let’s have a look at list comprehensions. A list comprehension in Haskell adheres to the following basic format (select input condition). For example:
haskell>[ x | x <-[1..200], x <11]
[1,2,3,4,5,6,7,8,9,10]

You can type expressions in the “select” section as follows

haskell> take 3[1,3..30]
[1,3,5]

(notice the “if”):

haskell>[if x <5 then x else x*2 | x <-[1..10]]
[1,2,3,4,10,12,14,16,18,20]

The condition (or predicate) section is applied to each item in the list you are selecting from. For example, the following statement returns only items where x mod 2 == 0.

haskell>[ x * x | x <-[1..10], x `mod`2==0]
[4,16,36,64,100]

Comprehensions can also allow multiple conditions:

haskell> [ x | x <-[1..10], x `mod`2==0, x /=6]
[2,4,8,10]

Even cooler than multiple conditions are multiple inputs. Notice x and y below; the result is all possible combinations of the elements in x and y.

haskell>[ x * y | x <-[1..5], y <-[10..17]][10,11,12,13,14,15,16,17,20,22,24,26,28,30,32,34,30,33,36,39,42,45,48,51,40,44,48,52,56,60,64,68,50,55,60,65,70,75,80,85]

Or using a tuple:

haskell>[(x, y)| x <-[1..3], y <-[4..7]]
[(1,4),(1,5),(1,6),(1,7),(2,4),(2,5),(2,6),(2,7),(3,4),(3,5),(3,6),(3,7)]

This post was very high level, but I hope it gave an idea of the power of list comprehensions. Imagine the succinct and clear code you could write by using these features. I for one am quite excited to delve deeper into the Haskell world to see what else it can do!