2

So I have a function in Haskell that I've simplified for the purpose of asking this question:

import Data.Foldable
import Data.Set

myFn :: Int -> Set Int
myFn a
  | a <= 0 = singleton 1
  | otherwise = foldMap helper (myFn (a - 1))

helper :: Int -> Set Int
helper a = insert (a + 2) (singleton a)

main :: IO ()
main = print . Data.Set.toList $ myFn 5

I want to have myFn's dependency on helper to be put into a Reader, since inversion of control allows me to switch implementations in my tests:

import Control.Monad.Reader
import Data.Foldable
import Data.Set

data MyEnv = MyEnv { helper' :: Int -> Set Int }
type MyReader = Reader MyEnv

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0 = return $ singleton 1
  | otherwise = do
      myFn' <- myFn (a - 1)
      helper'' <- asks helper'
      return (foldMap helper'' myFn')

helper :: Int -> Set Int
helper a = insert (a + 2) (singleton a)

main :: IO ()
main =
  let
    myEnv = MyEnv helper
  in
    print . Data.Set.toList $ runReader (myFn 5) myEnv

This works fine, except I don't like these three lines in particular:

myFn' <- myFn (a - 1)
helper'' <- asks helper'
return (foldMap helper'' myFn')

I feel like there should be a way to lift foldMap in the same way as mapM is a lifted version of map through its composition with sequence. Ideally, I would like those three lines to collapse down to one:

foldMapM helper'' (partitions (n - 1))

Assuming that: helper'' :: Int -> MyReader (Set Int)

This would of course require a foldMapM function with a signature similar to:

foldMapM
  :: (Monad m, Foldable t, Monoid n)
  => (a -> m n)
  -> m (t a)
  -> m n

I have tried so many things, but I just cannot seem to implement this function, though! Can anyone help?

arussell84
  • 2,443
  • 17
  • 18

1 Answers1

4

Basically, you would like to create Monad m => m a -> m b -> m c from a -> b -> c. That's exactly what liftM2 (from Control.Monad) does:

liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r

Promote a function to a monad, scanning the monadic arguments from left to right. For example,

liftM2 (+) [0,1] [0,2] = [0,2,1,3]
liftM2 (+) (Just 1) Nothing = Nothing

Therefore, it's as simple as using liftM2 foldMap:

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0    = return $ singleton 1
  | otherwise = liftM2 foldMap (asks helper') (myFn (a - 1))

Alternatively you can use <$> and <*> from Control.Applicative if you don't like additional parentheses:

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0    = return $ singleton 1
  | otherwise = foldMap <$> asks helper' <*> myFn (a - 1)

For more information, have a look at the Typeclassopedia.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 1
    Actually, `foldMapM f xs = liftM2 foldMap f xs` has signature `foldMapM :: (Monad m, Foldable t, Monoid n) => m (a -> n) -> m (t a) -> m n`. I tried this unsuccessfully before, since my function was of type `(a -> m n)`. I didn't think to do the `asks helper'` inline, though, to get `m (a -> n)`. This is perfect! – arussell84 Nov 01 '14 at 20:47