我刚刚到达了真实世界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表示这种附加列表的方式很糟糕,并教会我们差异列表技术。为什么不在这里使用?
顺便说一下,我喜欢这本书,这本书将成为一本书,我会在很长一段时间内阅读这本书。
答案 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