使用新类型构造函数进行映射有什么作用

问题描述

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也会出奇地有效。