【问题标题】:Avoiding ++ in Haskell在 Haskell 中避免使用 ++
【发布时间】:2016-12-02 07:50:00
【问题描述】:

this answer on CodeReview 中,提问者和回答者似乎都对 (++) 运算符不屑一顾。这是因为它的速度(导致算法在 O(n^2) 中显式运行,其中 n 是列表 iirc 的长度)?如果没有经过其他测试,这是否是预优化,因为 Haskell 以难以推理时间复杂度而闻名?其他人是否应该在他们的程序中避免使用 (++) 运算符?

【问题讨论】:

  • Haskell 列表是链表,附加列表是严格语言中的 O(n) 操作。然而,在 Haskell 中,它并不那么简单:懒惰允许将工作推迟到使用列表之前,从而有效地将成本分配到列表的长度上。正如您所提到的,这种懒惰会使时间和空间的复杂性难以在 Haskell 中进行推理,但我认为其他语言的相同建议也适用于 Haskell:首先为了清晰而编写,当且仅当你们都需要时才优化速度它会跑得更快并且你已经分析到找到一个瓶颈。
  • 两者兼而有之——你的代码更难推理,它通常会减慢你的代码速度,你可以经常使用它——但这完全取决于情况和 IMO 你的问题是不具体,不适合 SO
  • @Carsten:我对你关于“它通常会减慢你的代码”的评论感到有点困惑。虽然对于典型的尾递归阶乘示例来说确实如此,但这在日常程序中真的很常见吗?我认为 Haskell 之类的东西比 Ruby 和 Python 等更流行的外部语言之一的好处是富类型系统提供的优化。如果您真的认为我的问题不够具体,那么在我提供的示例中呢,只保留最后一个问题(这是我的意图,我认为 ThreeFx 想通了。
  • 如果我认为这个问题不适合 SO,你不必担心 - 这不是关于问题,而是关于 SO
  • 我不担心,我只是认为它适合 SO。 ThreeFx 能够在这方面为我提供很大帮助,我希望其他许多人也一样。

标签: haskell operators


【解决方案1】:

视情况而定。

考虑表达式

foldl (++) [] list

此表达式将列表列表连接成单个列表,但具有前面提到的二次复杂度。发生这种情况是因为(++) 的实现会遍历整个左侧列表并将每个元素添加到右侧列表中(当然要保持正确的顺序)。

使用正确的折叠,我们得到线性复杂度:

foldr (++) [] list

这是由于 (++) 运算符的实现,它仅遍历左侧参数并将其添加到右侧。

[1,2] ++ [3,4] ++ [5,6]

等于

-- Example as created by foldr
[1,2] ++ ([3,4] ++ [5,6])
== [1,2] ++ [3,4,5,6]
== [1,2,3,4,5,6] -- all good, no element traversed more than once

只遍历每个列表元素一次。

现在将括号切换到前两个列表更昂贵,因为现在一些元素被遍历多次,效率很低。

-- Example as created by foldl
([1,2] ++ [3,4]) ++ [5,6]
== [1,2,3,4] ++ [5,6]
== [1,2,3,4,5,6] -- the sublist [1,2] was traversed twice due to the ordering of the appends

总而言之,注意这种情况,你应该没事的。

【讨论】:

  • 这可以概括为 foldl 应该一直与左关联运算符一起使用吗?我们用 foldl' 做什么?编辑:如果一个操作是左右关联的,这是否意味着 foldl == foldr ?而且,在什么情况下 foldl != foldr ? Haskell 真的没有注意到这个瓶颈吗?我认为编译器应该是世界上最好的(这里特别指 GHC),不优化这似乎很愚蠢(尽管我可能不在我的联盟中......)。
  • @JackBrandt 不,至少不是一般规则。想象一下(^)(或(-)):这些运算符分别用于左右折叠时会产生不同的结果。这取决于你想要它做什么。 foldl' 完全不同,它是标准 foldl 的严格版本,并且几乎总是比 foldl 更受欢迎,除非您明确想要构建 thunk。
  • 你能详细说明为什么有人想要建立 thunk 吗?这似乎与大众的理解相悖。此外,数学示例对于理解 b/t 折叠的差异非常有用。
  • @JackBrandt 与其说是thunk,不如说是使用惰性消费函数的优势。鉴于foldl' 在其列表中的undefined 将崩溃,而懒惰的foldl 可能 返回,因为您的函数足够懒惰。您可能想看看this 示例。
  • 那么为什么编译器不能自动识别这些情况并在编译后的代码中提供最好的版本呢?
猜你喜欢
  • 2021-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-19
  • 2011-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多