I am learning Haskell and I the following expression on Haskell Wiki really puzzled me:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
I can't quite figure out why this works.
If I apply standard Currying logic (zipWith (+))
returns a function takes list as an argument and, in turn, returns another function that takes another list as an argument, and returns a list (zipWith::(a -> b -> c) -> [a] -> [b] -> [c]
). So, fibs
is a reference to a list (that has not yet been evaluated) and (tail fibs)
is the tail of the same (unevaluated) list. When we try to evaluate (take 10 fibs
), the first two elements are bound to 0
and 1
. In other words fibs==[0,1,?,?...]
and (tail fibs)==[1,?,?,?]
. After the first addition completes fibs
becomes [0,1,0+1,?,..]
. Similarly, after the second addition we get [0,1,0+1,1+(0+1),?,?..]
fibs !! 4
?EDIT2: I just found the above question asked and well answered here. I am sorry if I wasted anyone's time.
EDIT: here is a more difficult case to understand (source: Project Euler forums):
filterAbort :: (a -> Bool) -> [a] -> [a]
filterAbort p (x:xs) = if p x then x : filterAbort p xs else []
main :: Int
main = primelist !! 10000
where primelist = 2 : 3 : 5 : [ x | x <- [7..], odd x, all (\y -> x `mod` y /= 0) (filterAbort (<= (ceiling (sqrt (fromIntegral x)))) primelist) ]
Note that all (\y -> x mod y /= 0)...
How can referring to x here NOT cause infinite recursion?
I'll trace the evaluation for you:
> fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
With:
> zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
> zipWith _ _ _ = []
> tail (_:xs) = xs
> tail [] = error "tail"
So:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
↪ fibs = 0 : 1 : ((+) 0 1 : zipWith (+) (tail fibs) (tail (tail fibs)))
↪ fibs = 0 : 1 : 1 : ((+) 1 1 : zipWith (+) (tail (tail fibs)) (taii (tail (tail fibs)))))
↪ fibs = 0 : 1 : 1 : 2 : ((+) 1 2 : zipWith (+) (tail (tail (tail fibs))) (tail (taii (tail (tail fibs))))))
↪ fibs = 0 : 1 : 1 : 2 : 3 : ((+) 2 3 : zipWith (+) (tail (tail (tail (tail fibs)))) (tail (tail (taii (tail (tail fibs)))))))
Etc. So if you index into this structure, it will force each step of fibs to be evaluated until the index is found.
Why is this efficient? One word: sharing.
As fibs
is computed, it grows in the heap, recording values that have been computer. Later back references to fibs
get to reuse all previously computed values for fibs
. Free memoization!
What does that sharing look like in the heap?
As you ask for the object on the end of the list, Haskell computes the next value, records it, and moves that self-reference down a node.
The fact this terminates relies crucially on laziness.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With