我以为我很好地理解了惰性,直到我想出了下面的代码,它产生了一个
<<loop>>
错误。
weird = ([1],[1]) <> weird
main = print (head $ fst weird)
直观上,这就是我认为 Haskell 会做的事情: “我需要
weird
的第一个元素。而且我需要第一个元素的头。所以我需要计算 fst weird
。现在我知道来自半群实例实例的第一个 weird = [1] ++ fst weird
(或者我??) .所以太好了,我应该回来1
”
我哪里弄错了?
这里是模式匹配出了问题。事实上,如果我们查看 2 元组实例 [src]
的instance
,我们会看到:
Semigroup
所以这里需要两个 2 元组,然后它将两者结合起来。但这意味着它对第一个
和第二个操作数进行模式匹配。对于第二个操作数,存在问题,因为这是计算的结果,因此会触发系统评估这些操作数。 匹配可能看起来没有必要,但可以传递
instance (Semigroup a, Semigroup b) => Semigroup (a, b) where
(a,b) <> (a',b') = (a<>a',b<>b')
stimes n (a,b) = (stimes n a, stimes n b)
或其他导致循环的机制,就像这里所做的那样,因此代码基本上要求检查第二个操作数是否是 2 元组。
我们可以做的是使用无可辩驳的模式,这样我们就假设数据构造函数保存并且仅在必要时才将其解压。所以我们可以自己实现某种求和:
undefined
然后我们自己的实现适用于:
(<^>) :: (Semigroup a, Semigroup b) => (a, b) -> (a, b) -> (a, b)
~(a,b) <^> ~(a',b') = (a<>a',b<>b')
因此我们让实现变得更加懒惰以组合两个二元组。
我个人认为 2 元组(以及任何
n元组)上
weird = ([1],[1]) <^> weird
main = print (head $ fst weird)
等的组合可以用无可辩驳的模式来完成。我不知道是否有充分的理由说明基础包中的情况并非如此。