Monoid instance for IO

Gabriel Gonzalez gabriel439 at
Thu Nov 13 15:13:09 UTC 2014

I would like to add the following `Monoid` instance for `IO` to 

instance Monoid a => Monoid (IO a) where
    mempty  = pure mempty
    mappend = liftA2 mappend

I describe the benefit of this particular instance in this blog post:

... and Conal Elliot describes the general trick of recursively lifting 
`Monoid` instances in his type class morphisms paper:

The primary benefit of the `Monoid` instance is that it chains well with 
other `Monoid` instances in `base` to create derived `Monoid` 
instances.  The following types are examples of useful derived `Monoid` 

IO ()  -- Because `()` is a `Monoid`

a -> IO ()  -- Because `a -> r` is a `Monoid` if `r` is a `Monoid`

IO (a -> IO ())  -- This comment explains the utility of this instance:

Here are other alternatives that I considered:

**Alternative A)** Define a newtype for the `Monoid` instance, either 
specialized to `IO`:

newtype IOMonoid a = IOMonoid { getIOMonoid :: IO a } deriving (Functor, 
Applicative, Monad)

instance Monoid a => Monoid (IOMonoid a) where
     mempty = pure mempty
     mappend = liftA2 mappend

... or generalized to all applicatives:

newtype LiftMonoid f a = LiftMonoid ( getLiftMonoid :: f a }

instance (Applicative f, Monoid a) => Monoid (LiftMonoid f a) where ...

I prefer not to use a newtype because the principle benefit of a 
`Monoid` instance for `IO` is for the derived instances.  Using the 
example `IO (a -> IO ())` type, suppose that I had two values of that 
type which I wanted to mappend:

m :: IO (a -> IO ())
n :: IO (a -> IO ())

Using newtypes (either one), I'd have to write:

getNewtype (Newtype (fmap (fmap Newtype) m) <> Newtype (fmap (fmap 
Newtype) n))

... instead of just:

m <> n

**Alternative B)** Provide a different `Monoid` instance for `IO`, such 
as one that uses concurrency

There are two issues with this approach:

1.  There is not a well-defined semantics for non-`STM` concurrency that 
we could use to prove the `Monoid` laws
2.  Even if there were a well-defined semantics, it would be better 
suited as an `Alternative` instance instead of a `Monoid` instance

To clarify the latter point, Peaker convinced me [[ 
| here ]] that for certain `Applicative`s it's worth distinguishing the 
behavior of the `Alternative` instance from the behavior of the `Monoid` 
instance.  The `Monoid` instance can recursively delegate to the 
`Monoid` instance of the `Applicative`'s type parameter, whereas the 
`Alternative` instance cannot.

I also created a task on phabricator here since I'm used to the Github 
style of discussing issues on the repository issue tracker:

More information about the Libraries mailing list