danbroooks
danbroooks

Reputation: 2800

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?

Upvotes: 2

Views: 199

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 152682

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...

Upvotes: 6

chi
chi

Reputation: 116139

The issue here is that your Reader is a type alias instead of a newtype.

Using the former is disallowed by Haskell2010 (which is very conservative in what it is allowed), but GHC allows using type aliases in instances if you turn on the extension reported in the error you posted. Note that in such case it would define an instance for the expansion of the alias, e.g.

instance Monoid a => Monoid (r -> a) where ...

For a Reader type, I'd prefer to use a newtype, even if one needs to wrap/unwrap it when it is used.

Upvotes: 2

Related Questions