I’m working my way through Graham Hutton’s Programming in Haskell. Despite the fact that I’m not a mathematician (and I still believe that Haskell has syntax only a mathematician could love), it’s an accessible computer science text. It’s nicely clear, too, despite its relatively short length.

Most of the exercises are good too… except for one stumper in the list comprehensions chapter.

Given a list comprehension, defined in the function two_gens:

two_gens :: [(Integer, Integer)]
two_gens = [(x, y) | x <- [1, 2, 3], y <- [4, 5, 6]]

… which uses two generators in a single comprehension and produces:

[(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

… write two_comps which expresses the same concept using two comprehensions, each with a single generator. The book gives the hint to use concat and nest one comprehension within the other.

I stared at this problem for a long time, and eventually decided that two_gens felt like the most natural expression of the concept. After more thinking, I came up with a horrible hack:

two_comps :: [(Integer, Integer)]
two_comps = concat [ zip [x, x, x] [ y | y <- [4 .. 6] ] | x <- [1, 2, 3] ]

Did I miss the point of the exercise? Is there a better way to rewrite the expression? I’m both proud and horrified at my answer.

Update:

Paul McCann and Adam Turoff both responded to me privately with a more elegant solution:

[[(x, y) | y <- [4..6]] | x <- [1..3]]

I’m convinced that I tried that very code, but I obviously mistyped it, as I remember the error message warned that ghci didn’t understand x. (Now there’s a legitimate gripe for learning Haskell. A simple syntax error can lead to strange output from the type checker that a novice may not have the framework to understand.)

I still find the single comprehension form much more comprehensible.