I’m currently making my way through Learn You a Haskell for Great Good!, and I’m confused on the penultimate example in Chapter 2.
As a way of generating triples representing all right triangles with all sides that are whole numbers less than or equal to 10, he gives this definition:
rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]
What I’m specifically confused about is the fact that b is bound to a list that ranges from 1 to c, and similarly with a. If my understanding is correct, c will be evaluated to all values in the list it is bound to, but I still don’t see which value is being used for c in the range (e.g. all values of c, only the first c, etc.)
If it’s not too much, a step by step explanation of how this evaluates would be great. 🙂
Thanks in advance!
Let’s consider two simpler list comprehensions:
They’re almost the same, but in the second case,
branges from1toa, not1to3. Let’s consider what they’re equal to; I’ve formatted their values in such a way as to make a point.In the first example, the list comprehension draws every possible combination of elements from
[1..3]and[1..3]. But since we’re talking about lists, not sets, the order it does that in is important. Thus, in more detail, whatex1really means is this:abe equal to every possible value from its list.a, letbbe every possible value from its list.(a,b)is an element of the output listOr, rephrased: “for every possible value of
a, compute(a,b)for every possible value ofb.” If you look at the order of the results, this is what happens:ais equal to1, and we see it paired with every value ofb.ais equal to2, and we see every value ofb.ais equal to3and we see every value ofb.In the second case, much the same thing happens. But because
ais picked first,bcan depend on it. Thus:ais equal to1, and we see it paired with every possible value ofb. Sinceb <- [1..a], that meansb <- [1..1], and so there’s only one option.ais equal to2, and we see that paired with every possible value ofb. Now that meansb <- [1..2], and so we get two results.ais equal to3, and so we’re pickingb <- [1..3]; this gives us the full set of three results.In other words, because the list comprehensions rely on an ordering, you can take advantage of that. One way to see that is to imagine translating these list comprehensions into nested list comprehensions:
To get the right behavior,
a <- [1..3]must go on the outside; this ensures that thebs change faster than theas. And it hopefully makes it clear howbcan depend ona. Another translation (basically the one used in the Haskell 2010 Report) would be:Again, this makes the nesting very explicit, even if it’s hard to follow. Something to keep in mind is that if the selection of
ais to happen first, it must be on the outside of the translated expression, even though it’s on the inside of the list comprehension. The full, formal translation ofrightTriangleswould then beAs a side note, another way to write
rightTrianglesis as follows:You probably haven’t used
donotation yet, and certainly not for anything butIO, so I’m not saying you should necessarily understand this. But you can read thex <- listlines as saying “for eachxinlist“, and so read this as a nested loop:Note that the
continueonly skips to the next iteration of the innermost loop in this interpretation. You could also write it asWhere the last lines say “if
a^2 + b^2 == c^2, add(a,b,c)to the output list; otherwise, add nothing.” I only mention this because I thought seeing it written this way might help make the “nested loop”-type structure that’s going on clear, not because you should fully understanddo-notation while reading Chapter 2 of Learn You A Haskell 🙂