Haskell 函数中的自引用

发布于 2024-11-16 01:13:19 字数 1738 浏览 4 评论 0原文

我正在学习 Haskell,并且在 Haskell Wiki 上使用以下表达式 我真的很困惑:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

我不太明白为什么会这样。

如果我应用标准柯里化逻辑 (zipWith (+)) 返回一个函数,将列表作为参数,然后返回另一个函数,该函数将另一个列表作为参数,并返回一个列表 (zipWith::(a -> b -> c) -> [a] -> [b] -> [c])。因此,fibs 是对列表(尚未评估)的引用,(tail fibs) 是同一(未评估)列表的尾部。当我们尝试评估(取 10 个 fib)时,前两个元素绑定到 01。换句话说,fibs==[0,1,?,?...](tail fibs)==[1,?,?,?]。第一次加法完成后,fibs 变为 [0,1,0+1,?,..]。同样,在第二次加法之后,我们得到 [0,1,0+1,1+(0+1),?,?..]

  • 我的逻辑正确吗?
  • 有没有更简单的方法来解释这一点? (知道 Haskell 编译器对这段代码做了什么的人有什么见解吗?)(欢迎链接和参考)
  • 这种类型的代码确实只因为惰性求值才有效吗?
  • 当我撒谎时会发生什么评估! 4?
  • 此代码是否假设 zipWith 从头到尾处理元素? (我认为不应该,但我不明白为什么不)

编辑2:我刚刚发现上面的问题被问到并且得到了很好的回答 此处。如果我浪费了任何人的时间,我很抱歉。

编辑:这是一个更难理解的情况(来源:项目Euler 论坛):

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) ]

请注意 all (\y -> x mod y /= 0)... 这里引用 x 不会导致无限递归?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

呆萌少年 2024-11-23 01:13:19

我将为您跟踪评估:

> 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)))))))

等等。因此,如果您索引此结构,它将强制评估 fibs 的每一步,直到找到索引。

为什么这样有效?一个词:分享

当计算fibs时,它会在堆中增长,记录已计算的值。稍后对 fibs 的反向引用可以重用之前为 fibs 计算的所有值。免费记忆!

该共享在堆中是什么样子的?

在此处输入图像描述

当您询问列表末尾的对象时,Haskell 会计算下一个值并记录它,并将该自引用向下移动一个节点。

这个终止的事实主要取决于懒惰。

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?

enter image description here

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文