3

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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Chris Wohlert
  • 610
  • 3
  • 12
  • 2
    The `do` block is equivalent to `getA >> getB >> getC`. You could make use of `foldr1 (<>) <$> sequenceA [getA, getB, getC]`. – Willem Van Onsem Aug 19 '20 at 18:59
  • 5
    Perhaps you need some variant of the `Writer` monad, which does use `<>` on the implicitly written values. A main question is: when do you want to access the result of the concatenated `Element`s? At the very end of the monadic computation? Use `Writer`. In the middle? You probably need some variant of `State`. – chi Aug 19 '20 at 19:07

2 Answers2

6

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.

4castle
  • 32,613
  • 11
  • 69
  • 106
  • Oh this is really neat. If I want to access the Env, I would simply do so before I call unwrap? `runFunction = do { e <- ask; unwrap $ wrap getA }`? – Chris Wohlert Aug 20 '20 at 11:46
  • @ChrisWohlert Correct, and if you're using the version of `ask` from the `mtl` package, `Control.Monad.Reader`, then you could call `ask` directly from inside the `unwrap` region. Or else you could use `lift ask`. – 4castle Aug 20 '20 at 13:57
1
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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181