Search code examples
haskellhigher-kinded-typesmonoids

Implementing monoid for Reader


I may be naive in my thinking here but I think if the right hand value of a Reader is an instance of Monoid then a Monoid could be defined for Reader... Here is my implementation:

instance Monoid a => Monoid (Reader r a) where
  mempty = pure mempty
  mappend ra rb = (<>) <$> ra <*> rb

This however results in the following error:

    • Illegal instance declaration for ‘Monoid (Reader r a)’
        (All instance types must be of the form (T t1 ... tn)
         where T is not a synonym.
         Use TypeSynonymInstances if you want to disable this.)
    • In the instance declaration for ‘Monoid (Reader r a)’
    |
413 | instance Monoid a => Monoid (Reader r a) where
    |                      ^^^^^^^^^^^^^^^^^^^

I am unsure what this error actually means, and why I am unable to implement Monoid for Reader though I presume it is something to do with Reader being a higher kinded type?


Solution

  • There are two problems. The first is this:

    type Reader r = ReaderT r Identity
    

    For historical reasons, type synonyms are not allowed in instance declarations. This is the

    where T is not a synonym.
    

    part of the error. Luckily we can just expand the synonym; this would give us

    instance Monoid a => Monoid (ReaderT r Identity a)
    

    but now we would fall afowl of the other part of the error, namely:

    All instance types must be of the form (T t1 ... tn)
    

    Specifically, Identity is not a type variable, so it doesn't fit this form. Again this restriction is in place primarily for historical reasons. You can remove both restrictions by enabling two language extensions:

    {-# LANGUAGE TypeSynonymInstances #-}
    {-# LANGUAGE FlexibleInstances #-}
    

    However, in this case it's not needed. The preferable way is to actually use the prescribed form of instance declaration, so:

    instance (Applicative f, Monoid m) => Monoid (ReaderT r f m) where
        mempty = pure mempty
        mappend = liftA2 mappend
    

    This requires no extensions, and works not just for Reader but for ReaderT transforming any Applicative instance.

    However it does make an orphan instance; hence you should consider writing another newtype wrapper.

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    -- or, instead of GeneralizedNewtypeDeriving, write the obvious instances by hand
    newtype App f m = App { getApp :: f m } deriving (Functor, Applicative)
    instance (Applicative f, Monoid m) => Monoid (App f m) where
        mempty = pure mempty
        mappend = liftA2 mappend
    

    Then you can use App (Reader r) a as a monoid whenever a is a monoid. I seem to recall this existed somewhere in the standard libraries already, but I can't find it any more...