Reputation: 2800
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
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
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