问题描述
在thinking with types的第8章中,我了解到
的fmap Sum
部分
fastSum :: [Int] -> Int
fastSum = getSum . mconcat . fmap Sum
的运行时成本为O(n)
,而使用coerce
可以避免这种开销。
我知道newtype
没有表示开销,但是我不明白的是在列表上映射newtype构造函数的运行时效果如何。我认为这只会产生编译时开销,应该只是O(1)
,因为编译器只需要知道fmap SomeNewtypeCtr
表达式的类型即可。
解决方法
由于优化,很难理解Haskell在这种情况下到底能做什么。 Haskell仅要求结果是什么,而不是结果如何。
一些可能性:
-
fmap Sum
执行列表扫描,并复制每个单元格和每个元素; -
fmap Sum
执行列表扫描,并复制每个单元格但不复制元素(新单元格指向旧元素); -
fmap Sum
完全不扫描列表,并自动优化为无操作。
我尝试godbolt.org检查生成的Core和程序集。请注意,它仍然使用旧的GHC 8.6.2。尽管如此,我还是启用了(-O2
)并进行了优化
foo :: [Int] -> [Sum Int]
foo = fmap Sum
并获得
foo = Example.foo1 `cast` (.....)
Example.foo1 = \ (v_a1iF :: [Int]) -> v_a1iF
因此foo
成为身份函数,经过适当地强制,可以生成程序集
movq %r14,%rbx
andq $-8,%rbx
jmp *(%rbx)
粗略地讲,它应该等同于GHC运行时系统中的立即返回。
结论:Data.Coerce.coerce
非常好,因为它可以确保无操作,但是即使经过优化,即使是普通的Haskell也会出奇地有效。