concatMap f xs和concat $ map f xs之间的区别?

时间:2014-12-19 16:34:21

标签: haskell

据推测,他们完全相同,concatMap f xsconcat $ map f xs。为什么我会选择一个而不是另一个?

我想它可能是一种优化。如果是这样,GHC 7.8仍然如此吗?

1 个答案:

答案 0 :(得分:11)

您怀疑是concatMap f xs = concat (map f xs)的情况。因此,为了正确起见,您应该将它们视为可互换的。不过,我们可以检查他们的定义,以便学习更多内容。

concatMap               :: (a -> [b]) -> [a] -> [b]
concatMap f             =  foldr ((++) . f) []

concat :: [[a]] -> [a]
concat = foldr (++) []

特别是,这意味着concat . map f会扩展为foldr (++) [] . map f。现在使用一个称为"universal property of fold"的东西,我们可以看到任何foldr g z . map f = foldr (g . f) zgz的{​​{1}},例如选择({{1} }},f(++))我们在上面使用。这表明f就像我们想要的那样。[0]

那他们为什么定义不同呢?因为[]总是比concatMap f = concat . map f更快,因为在真正的病态情况下,后者建议两次单独的递归。由于懒惰,不太可能执行两次递归,所以给出了什么?

真正的原因是编译器可以使用更复杂的融合法,例如组合两个连续foldr ((++) . f) []或定义foldr (++) [] . map f和{之间的交互的融合法 {1}}。使用它们有点挑剔,因为它们依赖于能够查看代码片段的表面语法并检测可能的简化。 很多的工作用于持续解决融合法律。

但我们可以做的一件事是鼓励人们使用预先应用优化法则的高阶组合器。由于foldr永远不会比foldr更快,我们可以采取捷径并预先应用普遍法律简化。这将提高融合法在其他地方解雇的可能性,以最好地优化列表生产管道。

[0]为什么这项法律有效?粗略地说,unfoldr的普遍定律指出,如果你有foldr (++) [] . map ffoldr ((++) . f) []这样的foldrq,那么q [] = z 必须是 q (a:as) = f a (q as)。由于q可以显示为foldr f zq = foldr g z . map f,因此必须像我们想要的那样q [] = z