简体   繁体   中英

Real World Haskell book - asymptotic complexity of Logger monad example

I just reached Chapter 14 of Real World Haskell and was wondering about this yesterday.

In the Logger monad example, the bind function is implemented as follows:

-- (>>=) :: 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)

where the 2nd element in the injector function contains our log messages, which are continuously being appended using ++. (Read online around here for more context.)

My question is.. wouldn't that make the runtime complexity using this Logger quadratic to the number of messages?

If I'm wrong, please help provide the correct analysis and big oh notation.

If I'm right, I'm hoping people who have more experience/insights with Haskell and the book can tell me some reasons of choosing that implementation, and why it is ok. In the previous chapter of the book there's a section that says this way of appending of list is bad, and teaches us the difference list technique. Why is this not being used here?

BTW I love the book, it's going to be a book I'll read cover to cover since a long long time.

That's the standard (naive) encoding of the Writer monad , specialized to list output. It works well enough for the majority of uses, using the monoid instance for lists:

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

Alternatives with better complexity involve loggic to dlists , or even more efficiently, a text or builder monoid .

Depending on the associativity of the computation, yes, that will have quadratic complexity. For example, if your computation associates to the right:

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

then the complexity is linear. However, if it associates to the left:

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

then the complexity is quadratic. It is just "forwarding" to the properties of the underlying monoid, here [],(++) .

I imagine the reason for stating it this way is for simplicity. While it may not be efficient, it is completely obvious what is going on. However, as others have answered, this is just the Writer monad over the list monoid. You can get Writer by replacing [] with mempty and ++ with `mappend` , and then you can instantiate it with [] or DList or whatever you want. So using the concrete forms is not all that much clearer, IMO.

For comparison, Haskell Platform's monad library package "mtl" gets Writer from packages "transformers". The implementation of Writer for all Monoid types is in the source near here . The instance uses mappend instead of (++):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM