开发者

self-reference in Haskell functions

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),?,?..]

  • Is my logic correct?
  • Is there a simpler way to explain this? (any insights from people who know what Haskell complier does with this code?) (links and reference are welcome)
  • It is true that this type of code only works because of lazy evaluation?
  • What evaluations happen when I do fibs !! 4?
  • Does this code assume that zipWith processes elements first to last? (I think it should not, but I don't understand why not)

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?

self-reference in Haskell functions

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜