Haskell 不会懒惰地评估交错
Posted
技术标签:
【中文标题】Haskell 不会懒惰地评估交错【英文标题】:Haskell not lazy evaluating interleaving 【发布时间】:2014-08-29 03:04:17 【问题描述】:我正在解决一个问题(它来自一个 UPenn 类,但我没有接受它(只是通过它来学习 Haskell)),重点是构造一个 Stream(如下定义),它是定义的通过“标尺”(标尺!!n = 2 的最高幂的指数,将除以 n)。问题是我认为下面的标尺定义应该懒惰地评估,但它似乎是严格评估并无限运行。如果我通过添加像“nthStream 10 = streamRepeat 10”这样的终端案例来限制它,它会运行并生成输出到我想要正确的点。
data Stream a = Stream a (Stream a)
streamToList :: Stream a -> [a]
streamToList (Stream a rest) = [a] ++ streamToList rest
instance Show a => Show (Stream a) where
show s = show $ take 100 $ streamToList s
streamRepeat :: a -> Stream a
streamRepeat a = Stream a (streamRepeat a)
interleaveStreams :: Stream a -> Stream a -> Stream a
interleaveStreams (Stream a arest) (Stream b brest) = (Stream a (Stream b (interleaveStreams arest brest)))
nthStream :: Integer -> Stream Integer
nthStream n = interleaveStreams (streamRepeat n) (nthStream (n+1))
ruler :: Stream Integer
ruler = nthStream 0
谁能解释为什么统治者(和 nthStream)不懒惰地评估?
【问题讨论】:
不应该有streamRepeat
的基本情况吗?
@shree.pat18 streamRepeat
应该返回一个无限流,如果我没记错的话,很像 Prelude 中的 repeat :: a -> [a]
。
bheklir,你是对的。它应该返回一个无限流。例如:streamRepeat 3 = Stream 3 (Stream 3 (Stream 3....
@shree.pat18 我不确定我理解您所说的“终端情况”或“明确定义”是什么意思。导致它终止的原因是请求了多少无限数据结构,而不是问题中定义的任何函数。
@shree.pat18 你在考虑递归,但这是corecursion。
【参考方案1】:
我会尝试将您推向正确的方向,或者至少指出问题所在。函数nthStream
永远不会计算,甚至不会计算它的第一个元素,因为它是如何用interleaveStreams
定义的。举个例子,让我们计算出它对nthStream 0
的评估结果(为了简洁和可读性,我将nthStream
重命名为nth
,interleaveStream
重命名为interleave
,streamRepeat
重命名为repeat
,和Stream
到Strm
):
nth 0 = interleave (repeat 0) (nth 1)
= interleave (Strm 0 _0) (interleave (repeat 1) (nth 2))
= interleave (Strm 0 _0) (interleave (Strm 1 _1) (nth 2))
= interleave (Strm 0 _0) (interleave (Strm 1 _1) (interleave (repeat 2) (nth 3)))
= interleave (Strm 0 _0) (interleave (Strm 1 _1) (interleave (Strm 2 _2) (nth 3)))
我选择将从repeat
返回的每个流的尾部表示为_N
,其中N
是重复的数字。这些当前是 thunk,因为我们还没有请求它们的值。请注意正在构建的是interleave
s 链,而不是Strm
构造函数链。我们得到了我们感兴趣的每一个,但在评估第二个参数之前,它们永远无法从interleave
返回。由于第二个参数总是被简化为对interleave
的新调用,因此它永远不会减少。
这里有一个提示:你如何递归地定义interleaveStreams
,以便它只依赖于它的第一个参数已经被构造?即
interleaveStreams (Stream a arest) streamB = Stream a (???)
【讨论】:
这行得通吗? interleaveStreams (Stream a arest) streamB = Stream a (interleaveStreams streamB arest) ? @BrettJurman 我不知道,你告诉我。如果你做take 10 $ streamToList $ nthStream 0
,这会让nthStream
评估吗? interleaveStream
是否仍然采用两个流并将它们正确交错在一起?
@BrettJurman 基本上,我的意思是你已经找到了一个可能的解决方案,所以测试一下吧!它是否给您预期的响应?
它似乎确实有效。谢谢!抱歉,我没有测试它,我想睡觉,但我觉得我应该回应,因为你很有帮助【参考方案2】:
你的问题是线路
interleaveStreams (Stream a arest) (Stream b brest) = (Stream a (Stream b (interleaveStreams arest brest)))
为了使该函数甚至返回结果的开头,两个都必须对其参数进行评估,因为您直接在它们的构造函数上进行模式匹配。但是你在使用它
nthStream n = interleaveStreams (streamRepeat n) (nthStream (n+1))
这意味着nthStream n
在评估nthStream (n+1)
之前无法返回任何东西,这会为您提供无限递归。
要解决此问题,您可以将有问题的行中的第二个模式更改为显式 lazy 并使用 ~
:
interleaveStreams (Stream a arest) ~(Stream b brest) = (Stream a (Stream b (interleaveStreams arest brest)))
【讨论】:
我什至不知道“显式”懒惰的概念。鉴于这行得通,似乎我应该能够在没有它的情况下以某种方式编写它?是否有替代方案可以或多或少满足我的要求?我可能会感到困惑,但谢谢! @BrettJurman 当然有一种方法可以在没有无可辩驳的模式的情况下做到这一点,我希望你能从我的回答中弄清楚;)。除了对第二个参数进行模式匹配之外,有没有一种方法可以定义interleaveStreams
,您不需要立即需要每个流的两个头?检查我的答案以获得稍微更新的提示。
@bheklilr 我在您的回答线程中对此作出了回应。感谢您的提示!以上是关于Haskell 不会懒惰地评估交错的主要内容,如果未能解决你的问题,请参考以下文章