作家单子列表的效率如何?

时间:2018-12-14 19:37:04

标签: haskell linked-list monads writer

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

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

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

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

在Haskell中,懒惰会使列表编写者monad变得比OCaml这样的急切语言更有效吗?如果不是这样,那么对我的工作量有什么有效的替代方法?

1 个答案:

答案 0 :(得分:5)

与左相关的h = 0效率低下,因为最左边的列表被遍历了多次,每个包围(++)一次。右相关的(++)很好(至少,不能直接使用(++)来提高效率)。

标准(:)转换器(和WriterT编写者)以与绑定关联相同的方式将其调用与(,)关联。因此,通过扩展前面的讨论,与左相关的(++)会出现问题,而与右相关的(>>=)会很好。特别是,这意味着会产生抽象费用。如果在重构中要拉出下面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]++))

这将神奇地将您的(++)关联到右侧(哇!每次我重新确定其工作原理时,我的脑海都震撼了)。代价是差异列表的每个观察(即从差异列表到标准列表的转换)都很昂贵(而以前选择裸列表的情况下,多个观察的成本不超过一个观察)。如果您只有一个消费者(例如,一次对runWriterT的顶级调用,一次又一次地将列表弄平了),那么这就渐近而言没问题,但是如果您发现自己正在调用listen或{ {1}}并经常检查差异列表,您可能不想选择它。

如果这些折衷选择都不对您有利,那么第三种选择是使用手指树,例如pass的观察是免费的(与差异列表不同),并且两端的连接在较短的自变量中是对数时间(与标准列表不同,在第一个自变量中它是线性的),但是常数是足够高,您可以在很多情况下注意到它。