You Should Learn a Language with List Comprehensions

Tue 01 November 2011 | -- (permalink)

In a post linked from Hacker News today a guy named David Jacobs explains how he moved from Java to Ruby because of awesomeness like first class functions, giving an example of how Ruby lets you create your own function and pass it to in order to filter an array on whatever criteria you like. He uses selecting only the even numbers from a list as his example.

array = [1, 2, 3, 4, 5, 6]
criterion = lambda {|n| n % 2 == 0 } &criterion
# => [2, 4, 6]

The equivalent Java code was huge, and would would have to be 90% copy/pasted to make similar filtering functions like "give me just the odd numbers". Well done Ruby. David's post goes on to say that he's now learning Python (because of the availability of scientific computing packages like numpy and scipy). He also says Python is ugly. BATTLE STATIONS! Here's how Python does that.

somenumbers = [1, 2, 3, 4, 5, 6]

[x for x in somenumbers if x % 2 == 0]
# result is [2, 4, 6]

I \<3 that you can use list comprehensions to do something like that without even having to declare a function or lambda. In Ruby you can accomplish the same thing with either chained select/collect or map/compact calls, but I'll pick Python's list comprehensions in a beauty contest.

As geeks do (and as I've done here), the commenters on David's post rushed to defend their favorite languages. In Java's case, they say, the extra code is buying you type safety that Ruby (or Python) can't give you.

I call shenanigans. You can have both. Behold, the Haskell version:

justEvens :: Integral a => [a] -> [a]
justEvens xs = [x | x < - xs, even x]
-- load that up in ghci and you get this:
-- ghci: justEvens [1, 2, 3, 4, 5, 6]
-- [2,4,6]

It's a function around a list comprehension. The type declaration on the first line gives you the type safety that Python and Ruby lack. The second line does the work. So it is possible to have terse, clean, expressive syntax at the same time as first class functions and type safety. That's what makes Haskell my current geek-crush.

(Aside: Python's list comprehensions were specifically borrowed from Haskell. Python's syntax just uses words where Haskell's notation uses symbols.)

UPDATE: As effusively as I praised Haskell up there, I still sold it a little short by neglecting to mention Haskell's type inference. Long story short, you don't even need the type declaration line in the Haskell example above. Given this definition...

justEvens' xs = [x | x < - xs, even x]

... you can inspect that expression in ghci and check its type signature against the one we explicitly declared:

ghci: justEvens' [1, 2, 3, 4, 5, 6]
ghci: :t justEvens'
justEvens' :: Integral t => [t] -> [t]
ghci: :t justEvens
justEvens :: Integral a => [a] -> [a]

Their type signatures are the same. Haskell took the implication of us using the 'even' function inside the list comprehension, and from there inferred that our function must accept a list of Integrals. (Haskell's strict type system results in there being a few kinds of integers.)

Now this doesn't mean that you should turn Haskell programs into code golf and omit type signatures wherever you can. It's wise to keep them because 1) they serve as a check on your understanding of the function being the same as the Haskell compiler's, and 2) they'll reduce the amount of time it takes future programmers to understand your code.