You find it "shocking" because you're not expecting it. Once you get used to it... OK, actually it still trips people over. But after a while, you eventually wrap your mind around it.
How Haskell works is this: When you call a function, nothing happens! The call is noted down somewhere, and that is all. This takes approximately no time at all. Your "result" is actually just an "I owe you" telling the computer what code to run to get to the result. Not the entire result, mind you; just the first step of it. For something like an integer, there is only one step. But for a list, each element is a separate step.
Let me show you a simpler example:
print (take 10 ([1..] ++ [0]))
I spoke with a C++ programmer who was "shocked" that this works. Surely the "++[0]
" part has to "find the end of the list" before it can append zero to it? How can this code complete in finite time?!
It looks like this builds [1..]
(in infinite list), and then ++[0]
scans to the end of this list and inserts a zero, and then take 10
trims off just the first 10 elements, and then it prints. That would, of course, take infinite time.
So here's what actually happens. The outer-most function is take
, so that's where we start. (Weren't expecting that, eh?) The definition of take
is something like this:
take 0 ( _) = []
take n ( []) = []
take n (x:xs) = x : (take (n-1) xs)
So clearly 10 != 0, so the first line does not apply. So either the second or third line applies. So now take
looks at [1..] ++ [0]
to see if it's an empty list, or a non-empty list.
The outer-most function here is (++)
. It's definition looks similar to
( []) ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
So we need to figure out which equation applies. Either the left argument is an empty list (line one applies), or it isn't (line two applies). Well, since [1..]
is an infinite list, line two always applies. So the "result" of [1..] ++ [0]
is 1 : ([2..] ++ [0])
. As you can see, this isn't completely executed; but it's executed far enough to tell this is a non-empty list. Which is all that take
cares about.
take 10 ([1..] ++ [0])
take 10 (1 : ([2..] ++ [0]))
1 : take 9 ([2..] ++ [0])
1 : take 9 (2 : ([3..] ++ [0]))
1 : 2 : take 8 ([3..] ++ [0])
1 : 2 : take 8 (3 : ([4..] ++ [0]))
1 : 2 : 3 : take 7 ([4..] ++ [0])
...
1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : take 0 ([11..] ++ [0])
1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : []
Do you see how this unwinds?
Now, returning to your specific question: the (==)
operator takes a pair of lists and iterates over both of them, comparing them element by element to ensure they are equal. As soon as a difference is found, it immediately aborts and returns false:
( []) == ( []) = True
(x:xs) == (y:ys) = (x == y) && (xs == ys)
( _) == ( _) = False
If we now try, say, prime 6
:
prime 6
factors 6 == [1,6]
??? == [1,6]
1 : ??? == [1,6]
??? == [6]
2 : ??? == [6]
False