Is there a Monad which collects results and `mappend`s them?

Viewed 132

I have the following pattern of a Reader with a Semigroup Element:

runFunction :: Reader Env Element
runFunction = do
  a <- getA
  b <- getB
  c <- getC
  return $ a <> b <> c

Where getA :: Reader Env Element.

Is there a way to:

runFunction = do
  getA
  getB
  getC

I feel like I see this pattern alot, where I imperatively chain monadic calls, and they get turned into a single element at the end.

Note: I don't want to do getA >>= getB >>= getC since getB isn't :: Element -> Reader Env Element

It feels like a State Monad, that automatically modifies state with <>, but I don't know.

Working with monadic code is still quite fresh to me.

2 Answers

The WriterT monad transformer can be used to build up a monoidal value with each action.

You can use lift to wrap your Reader actions into the WriterT monad transformer, then move the result of each action into the monad's environment by using tell, sequence the actions, and then unwrap the result back to a single Reader action using execWriterT.

Here is what I mean:

import Control.Monad.Writer

wrap :: (Monad m, Monoid w) => m w -> WriterT w m ()
wrap m = lift m >>= tell

unwrap :: (Monad m) => WriterT w m a -> m w
unwrap = execWriterT

runFunction :: Reader Env Element
runFunction = unwrap $ do
    wrap getA
    wrap getB
    wrap getC

As you can see, this generalizes to any monad, not just Reader.

runFunction = fmap mconcat . sequence $ [getA, getB, getC]

whatever the monad is.

fmap mconcat . sequence
  :: (Monoid b, Monad f, Traversable t) => t (f b) -> f b

List, [], is a Traversable.

With Semigroups, there's sconcat.

Related