Feedback on simple Merge algorithm I am writing a simple merge algorithm in Haskell. I wanted to add simple logging, hence am using the Writer Monad to include logging.

type ArrayWithLogging a = WriterT [String] Identity [a]

merge :: (Show a, Ord a) =&gt; ArrayWithLogging a -&gt; ArrayWithLogging a -&gt; ArrayWithLogging a
merge al bl = do
    a &lt;- al
    b &lt;- bl
    case (a, b) of
        ([], b') -&gt; tell ["Returning " ++ show b'] &gt;&gt; return b'
        (a', []) -&gt; tell ["Returning " ++ show a'] &gt;&gt; return a'
        (a'@(x:xs), b'@(y:ys)) -&gt; if (x &lt;= y)
                                    then tell ["Min " ++ show x ++ " merging " ++ show xs ++ " with " ++ show b] 
                                        &gt;&gt; ((merge (return xs) (return b')) &gt;&gt;= \z -&gt; return (x : z))
                                  else tell ["Min " ++ show y ++ " merging " ++ show a ++ " with " ++ show ys] 
                                        &gt;&gt; ((merge (return a') (return ys)) &gt;&gt;= \z -&gt; return (y : z))


I feel there are too many returns inside the monadic context. Is this style correct?
Let's start simple. First of all, your code seems fine. However, as you've noticed yourself, there's an abundance of returns. Several of them don't seem necessary, so let us inspect some patterns:

((merge (return a') (return ys)) &gt;&gt;= \z -&gt; return (y : z))


The pattern x &gt;&gt;= \z -&gt; return (f z) is the same as fmap f x. That already reduces the number of returns by two:

type ArrayWithLogging a = WriterT [String] Identity [a]

merge :: (Show a, Ord a) =&gt; ArrayWithLogging a -&gt; ArrayWithLogging a -&gt; ArrayWithLogging a
merge al bl = do
    a &lt;- al
    b &lt;- bl
    case (a, b) of
        ([], b') -&gt; tell ["Returning " ++ show b'] &gt;&gt; return b'
        (a', []) -&gt; tell ["Returning " ++ show a'] &gt;&gt; return a'
        (a'@(x:xs), b'@(y:ys)) -&gt; if (x &lt;= y)
                                    then tell ["Min " ++ show x ++ " merging " ++ show xs ++ " with " ++ show b] 
                                        &gt;&gt; fmap (x:) (merge (return xs) (return b'))
                                    else tell ["Min " ++ show y ++ " merging " ++ show a ++ " with " ++ show ys] 
                                        &gt;&gt; fmap (y:) (merge (return a') (return ys))


Now, for the next step, let's get rid of the WriterT monad in the first two arguments. There's no reason for that, and it further removes the return:

merge :: (Show a, Ord a) =&gt; [a] -&gt; [a] -&gt; ArrayWithLogging a
merge a b = do
    case (a, b) of
        ([], b') -&gt; tell ["Returning " ++ show b'] &gt;&gt; return b'
        (a', []) -&gt; tell ["Returning " ++ show a'] &gt;&gt; return a'
        (a'@(x:xs), b'@(y:ys)) -&gt; if (x &lt;= y)
                                    then tell ["Min " ++ show x ++ " merging " ++ show xs ++ " with " ++ show b] 
                                        &gt;&gt; fmap (x:) (merge xs b')
                                    else tell ["Min " ++ show y ++ " merging " ++ show a ++ " with " ++ show ys] 
                                        &gt;&gt; fmap (y:) (merge a' ys)


We can still get the old variant by using the following merge':

merge' a1 b1 =
  a &lt;- a1
  b &lt;- b1
  merge a b


Now that we've reduced function pretty far (but didn't lose any functionality!), we can go ahead and change some indentation to make our intend more clear. Furthermore, let us also introduce a small helper called message:

merge :: (Show a, Ord a) =&gt; [a] -&gt; [a] -&gt; ArrayWithLogging a
merge a b = do
    case (a, b) of
        ([], b') -&gt; tell ["Returning " ++ show b'] &gt;&gt; return b'
        (a', []) -&gt; tell ["Returning " ++ show a'] &gt;&gt; return a'
        (a'@(x:xs), b'@(y:ys)) -&gt; 
            if (x &lt;= y)
                then message x xs b  &gt;&gt; fmap (x:) (merge xs b')
                else message y a  ys &gt;&gt; fmap (y:) (merge a' ys)
  where
   message e ls rs = tell ["Min " ++ show e ++ " merging " ++ show ls ++ " with " ++ show rs]


OK, let's reiterate our steps:


We changed x &gt;&gt;= \z -&gt; (f z) to fmap f x
We changed the arguments into their non-monad variant
We introduced another function to keep the old behaviour (if necessary)
We changed the indentation to make sure that it looks nicer.
We introduced a small helper to make sure that we don't repeat ourselves.


And those are all the non-opinionated (well, except the last one) changes I'd recommend to you. In my personal opinion, the function gets even a little bit nicer if we use pattern matching on the arguments:

type ArrayWithLogging a = WriterT [String] Identity [a]

merge :: (Show a, Ord a) =&gt; [a] -&gt; [a] -&gt; ArrayWithLogging a
merge a        []     = tell ["Returning " ++ show a] &gt;&gt; return a
merge []       b      = tell ["Returning " ++ show b] &gt;&gt; return b
merge a@(x:xs) b@(y:ys)
  | x &lt;= y    = message x xs b  &gt;&gt; fmap (x:) (merge xs b)
  | otherwise = message y a  ys &gt;&gt; fmap (y:) (merge a ys)
  where
    message e ls rs = tell ["Min " ++ show e ++ " merging " ++ show ls ++ " with " ++ show rs]

merge' :: (Show a, Ord a) =&gt; ArrayWithLogging a-&gt; ArrayWithLogging a-&gt; ArrayWithLogging a
merge' a b = do
   a1 &lt;- a
   b1 &lt;- b
   merge a1 b1