【问题标题】:How efficient is the writer monad for lists?列表的 writer monad 的效率如何?
【发布时间】:2018-12-14 19:37:03
【问题描述】:

Haskell writer monad 在列表 (Writer [w] a) 上的实现将使用 ++ 添加项目。因此,如果我在列表编写器 monad 中编写此代码:

do
  tell [a, b, c]
  tell [d]

列表将附加[a, b, c] ++ [d]。在 OCaml 中工作后,我认为列表应该使用 cons 运算符 (:) 而不是串联运算符 (++) 构建,因为后者的第一个参数是 O(n)。

我的工作量一次向 writer monad 添加一个“消息”,因此 ++ 的第二个参数通常是一个单例列表。

在 Haskell 中,懒惰会使列表编写器 monad 比在 OCaml 等热切语言中更有效吗?如果不是,对于我的工作量来说,什么是有效的替代方案?

【问题讨论】:

标签: haskell linked-list monads writer


【解决方案1】:

左侧关联的(++)s 效率低下,因为最左侧的列表被遍历多次,每个封闭的(++) 遍历一次。右关联的(++) 很好(至少,直接使用(:) 不能提高效率)。

标准的WriterT 转换器(和(,) 编写器)将它们的调用关联到(++),其绑定方式与它们的关联方式相同。因此,通过前面的讨论,左关联的(>>=)s 将是有问题的,而右关联的则很好。特别是,这意味着存在抽象成本。如果,在重构中,要拔出下面 do 块的前两行:

x = do
    tell a
    tell b
    tell c

分成一个单独的定义,也许是因为它们经常发生:

y = do
    tell a
    tell b

x = do
    y
    tell c

此重构将一个绑定重新关联到左侧,因此成本略高。

如果这让您担心,您可以通过使用标准差异列表技巧作为您的幺半群来选择稍微不同的权衡。所以:

do
    tell (Endo ([a,b,c]++))
    tell (Endo ([d]++))

这会神奇地将您的(++)s 重新关联到右侧(哇!每次我重新弄清楚它是如何工作的时候都让我大吃一惊)。成本是差异列表的每次观察(即从差异列表到标准列表的转换)都是昂贵的(而之前选择裸列表,多次观察的成本不超过一次观察)。如果你只有一个消费者——比如说,对runWriterT 的顶级调用,它一劳永逸地压平了列表——这渐近没有问题,但是如果你发现自己调用listenpass 并检查差异列表经常,你可能不想选择这个。

如果这些权衡对您来说都不合适,第三种选择是使用手指树,例如Seq,观察是免费的(与差异列表不同),两端的串联是较短参数中的对数时间(与标准列表不同,它在第一个参数中是线性的),但常数足够高在很多情况下你可以注意到它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-03
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-16
    • 2018-12-12
    相关资源
    最近更新 更多