为什么ghc中的默认sum
比其foldl'
(foldl
的{{1}})等效慢约10倍?如果是这种情况,为什么不使用foldl'
实现?
import Data.List
> foldl' (+) 0 [1..10^7]
50000005000000
(0.39 secs, 963,528,816 bytes)
> sum [1..10^7]
50000005000000
(4.13 secs, 1,695,569,176 bytes)
为了完整性,此处还有foldl
和foldr
的统计信息。
> foldl (+) 0 [1..10^7]
50000005000000
(4.02 secs, 1,695,828,752 bytes)
> foldr (+) 0 [1..10^7]
50000005000000
(3.78 secs, 1,698,386,648 bytes)
看起来sum
是使用foldl
实现的,因为它们的运行时类似。在ghc 7.10.2上测试。
答案 0 :(得分:10)
sum
函数是使用GHC中的foldl
实现的:
-- | The 'sum' function computes the sum of a finite list of numbers.
sum :: (Num a) => [a] -> a
{-# INLINE sum #-}
sum = foldl (+) 0
可以看出in the source。
必须这样,因为它是规范in the Haskell report。
理由很可能是对于列表的某些惰性元素类型,foldl
是正确的做法。 (我个人认为foldl
几乎总是错误的,只应使用foldl'
。)
通过充分优化,GHC将内联该定义,将其专门化为手头的元素类型,注意循环是严格的,并在每次迭代中强制累加器的评估;正如@AndrásKovács所观察到的那样,有效地将其转变为foldl'
。
自GHC-7.10起,sum
itself是Foldable
类型类的方法,默认定义通过foldMap
。但是,instance Foldable []
会使用sum
的上述定义来覆盖此内容。
答案 1 :(得分:0)
为了补充@Joachim Breitner的回答,我找到了这个blog post,这是一个非常有趣的读物(摘自reddit讨论,感谢@ZhekaKozlov的链接)。
当Haskell 1.0在24年前的这一天发布时,根本没有seq函数,所以除了以“经典”的方式定义foldl之外别无选择。
最后,经过多次讨论六年后,我们在Haskell 1.3中获得了seq函数。虽然实际上在Haskell 1.3 seq中是Eval类的一部分,但是你无法在任何地方使用它,例如在foldl中。在Haskell 1.3中,你必须定义foldl'类型:
foldl' :: Eval b => (b -> a -> b) -> b -> [a] -> b
Haskell 1.4和Haskell 98摆脱了seq的Eval类约束,但foldl没有改变。 Hugs和GHC以及其他实现添加了非标准的foldl'。
我怀疑人们认为这是一个兼容性和惯性问题。很容易添加一个非标准的折叠'但你不能这么容易地改变标准。
我怀疑如果我们从一开始就有seq,那么我们就可以使用它来定义foldl。
Haskell的前身语言之一Miranda在Haskell 1.0之前已经有5年了。
顺便说一句,我已经设法通过使用
来减少20毫秒foldl1' (+) [1..10^7]
所以,我猜foldl1'
应该是sum
和product
的默认值(特殊处理空列表)。