Monadic transformation on a list, done in three ways

I am working my way through the Haskell Book and ran into the exercises at the end of the Monad chapter(18). One in particular asked to define a function with the following signature:

meh :: Monad m => [a] -> (a -> m b) -> m [b]


So I did. I found this routine difficult to implement at first, but once I understood what I was doing and what I needed to do a little better I figured it out.

I have three versions of the routine, my first attempt, a slightly refactored attempt, and a final attempt using do-notation. I was wondering if anyone can give advice on how I could potentially refactor this routine further, or how I can make my implementation better. I feel like I can make the routine far cleaner than I have it, but I am not sure how.

WARNING: If you are reading the Haskell Book and want to work through the exercises yourself, I recommend you do not read any further

Here are all three implementations

-- Attempt #1 with helper routine
meh :: Monad m => [a] -> (a -> m b) -> m [b]
meh list fn = doWork list (return $[]) fn where doWork (x:xs) base f | length xs == 0 = (addToMonad x base f) >>= (\x -> return$ reverse x)
| otherwise = doWork xs (addToMonad x base f) f

addToMonad x baseMonad fn = do
someVal <- (fn x)
base <- baseMonad
return $someVal:base -- Attempt #2 with a fold meh' :: Monad m => [a] -> (a -> m b) -> m [b] meh' list fn = (foldl (\acc x -> (fn x) >>= (\y -> acc >>= (\z -> return$ y:z))) (return $[]) list) >>= (\x -> return$ reverse x)

-- Attempt #3 with a fold and do-notation
mehDo :: Monad m => [a] -> (a -> m b) -> m [b]
mehDo list fn = do
final <- (foldl (\acc x -> do
val <- (fn x)
mList <- acc
return $val:mList) (return$ []) list)
return $reverse final  I want to say my last implementation is the easiest to read (it would be easier with better names, IMO) but I don't really know that it is. I am not super experienced in the ways of Haskell, so to be frank I read the routines really slowly regardless. Anyway, advice or tips on how to make this cleaner would be much appreciated. 2 Answers I'd say that the idiomatic way to write this function in Haskell is by composing a more primitive one with a map. Indeed, given the type signature of meh, it is very tempting to "prepare" a list of all the m b actions and then combine them in order: meh :: Monad m => [a] -> (a -> m b) -> m [b] meh xs f = combine (f <$> xs)


Now, combine has type [m b] -> m [b]. To process that list, we can use a fold. You have used foldl in your examples but you ended up reversing the list which is a clear sign that you may have wanted a foldr instead. And we can indeed do that:

combine :: Monad m => [m b] -> m [b]
combine = foldr (\ x xs -> (:) <$> x <*> xs) (return [])  Here the functor (<$>) and applicative (<*>) combinators allow to write something that looks a bit direct style (we are basically using (:) to construct a list by giving its head and tail) but that takes care of the monadic actions.

As I don't have a copy of the Haskell Book, I don't know what is already known at that point. But meh is a standard function, namely flip mapM or forM:

import Control.Monad (forM)

meh :: Monad m => [a] -> (a -> m b) -> m [b]
meh = forM


But that's cheating, right? So we can try another approach instead, namely split the functionality into two functions:

meh :: Monad m => [a] -> (a -> m b) -> m [b]
meh xs f = sequence' (map f xs)


sequence is again a standard function, so we're still somewhat cheating. However, we can write it our own:

sequence' :: Applicative f => [f a] -> f [a]
sequence' []     = pure []
sequence' (x:xs) = (:) <\$> x <*> sequence' xs


Note that Applicative is enough to define both meh and sequence'. The original sequence is usually defined as follow, by the way:

sequence []     = return []
sequence (x:xs) = do
y  <- x
ys <- sequence xs
return (y : ys)


Which isn't too far off of your last variant. But if you're already using do notation, I would pair it with pattern-matching:

mehDo :: Monad m => [a] -> (a -> m b) -> m [b]
mehDo []     f = return []
mehDo (x:xs) f = do
y  <- f x
ys <- mehDo xs f
return (y : ys)


Which, in my point of view, is the easiest variant to read. But if you use meh in your own code, you're going to use forM either way.

• This was very informative. It is neat to see things like Foldable and Traversable that can be exchanged with lists as you get deeper into Haskell. Thanks for your time. – Carson Mar 14 '17 at 14:20