真实世界Haskell书 - Logger monad例子的渐近复杂性

时间:2011-04-12 17:16:50

标签: algorithm complexity-theory time-complexity haskell

我刚刚到达了真实世界Haskell的第14章,并且昨天对此感到疑惑。

在Logger monad示例中,bind函数实现如下:

-- (>>=) :: Logger a -> (a -> Logger b) -> Logger b
    m >>= k = let (a, w) = execLogger m
                  n      = k a
                  (b, x) = execLogger n
              in Logger (b, w ++ x)

其中,注入器函数中的第二个元素包含我们的日志消息,这些消息是使用++连续添加的。 (有关更多背景信息,请在here附近在线阅读。)

我的问题是..不会使运行时的复杂性使用这个Logger二次到消息的数量吗?

如果我错了,请帮助提供正确的分析和大记法。

如果我是对的,我希望那些对Haskell和本书有更多经验/见解的人可以告诉我选择该实现的一些原因,以及为什么它没问题。 在本书的前一章中,a section表示这种附加列表的方式很糟糕,并教会我们差异列表技术。为什么不在这里使用?

顺便说一下,我喜欢这本书,这本书将成为一本书,我会在很长一段时间内阅读这本书。

3 个答案:

答案 0 :(得分:5)

这是Writer monad的标准(天真)编码,专门用于列出输出。它适用于大多数用途,使用monoid实例进行列表:

instance Monoid [a] where
        mempty  = []
        mappend = (++)

具有更高复杂性的替代方案涉及dlists的逻辑,甚至更有效的文本或builder monoid

答案 1 :(得分:2)

根据计算的相关性,是的,这将具有二次复杂性。例如,如果您的计算与右侧相关联:

log 0 >> (log 1 >> (log 2 >> log 3))
然后,复杂性是线性的。但是,如果它与左侧相关联:

((log 0 >> log 1) >> log 2) >> log 3

然后复杂性是二次的。它只是“转发”到底层幺半群的属性,这里是[],(++)

我想这样说的原因是为了简单起见。虽然它可能效率不高,但发生的事情是完全明显的。但是,正如其他人所回答的那样,这只是列表中的Writer monad monoid。您可以将Writer替换为[],将mempty替换为++来获取`mappend`,然后您可以使用[]或{{1}对其进行实例化或者你想要的任何东西。所以使用具体形式并不是那么清楚,IMO。

答案 2 :(得分:1)

为了比较,Haskell平台的monad库包“mtl”从包“transformers”中获取Writer。所有Monoid类型的Writer的实现都在here附近。该实例使用mappend而不是(++):

instance (Monoid w, Monad m) => Monad (WriterT w m) where
    return a = WriterT $ return (a, mempty)
    m >>= k  = WriterT $ do
        ~(a, w)  <- runWriterT m
        ~(b, w') <- runWriterT (k a)
        return (b, w `mappend` w')
    fail msg = WriterT $ fail msg