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

What is the effect of mapping with a newtype constructor

thinking with types 的第 8 章中,我了解到

fmap Sum 部分
fastSum :: [Int] -> Int
fastSum = getSum . mconcat . fmap Sum

O(n) 的运行时间成本,而使用 coerce 可以避免这种开销。

我知道 newtypes 没有表示开销,但我不明白的是将新类型构造函数映射到列表上的运行时效果是什么。我认为这只会有一个编译时开销,它应该只是 O(1),因为编译器只需要知道 fmap SomeNewtypeCtr 表达式的类型。

由于优化,很难理解 Haskell 在这种情况下究竟做了什么。 Haskell 只要求结果是什么,而不是如何获得。

一些可能性:

  • fmap Sum执行列表扫描,并复制每个单元格和每个元素;
  • fmap Sum执行列表扫描,复制每个单元格但不复制元素(新单元格指向旧元素);
  • fmap Sum根本不扫描列表,自动优化为no-op.

我尝试 godbolt.org 检查生成的核心和程序集。请注意,它仍然使用旧的 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 运行时系统中的立即数 return。

结论:Data.Coerce.coerce 非常好,因为它确保了 no-op,但即使是普通的 Haskell,经过优化后,也可以非常有效。