使用新类型构造函数进行映射有什么影响
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
可以避免这种开销。
我知道 newtype
s 没有表示开销,但我不明白的是将新类型构造函数映射到列表上的运行时效果是什么。我认为这只会有一个编译时开销,它应该只是 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,经过优化后,也可以非常有效。
在 thinking with types 的第 8 章中,我了解到
的fmap Sum
部分
fastSum :: [Int] -> Int
fastSum = getSum . mconcat . fmap Sum
有 O(n)
的运行时间成本,而使用 coerce
可以避免这种开销。
我知道 newtype
s 没有表示开销,但我不明白的是将新类型构造函数映射到列表上的运行时效果是什么。我认为这只会有一个编译时开销,它应该只是 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,经过优化后,也可以非常有效。