Reputation: 101
I have the following code:
data Logger a = Logger { getLog :: (a, [String]) }
instance Functor Logger where
fmap f (Logger (x, log)) = Logger (f x, log)
instance Applicative Logger where
pure x = Logger (x, [])
(Logger (f, log1)) <*> (Logger (x, log2)) = Logger (f x, log1 `mappend` log2)
instance Semigroup a => Semigroup (Logger a) where
(Logger (x, log1)) <> (Logger (y, log2)) = Logger (x <> y, log1 <> log2)
instance Monoid a => Monoid (Logger a) where
mempty = Logger (mempty, [])
instance Monad Logger where
return x = Logger (x, [])
(Logger (x, log)) >>= f = Logger (res, log `mappend` newLog)
where (Logger (res, newLog)) = f x
instance Fail.MonadFail Logger where
fail msg = mempty
I get the following error:
• No instance for (Monoid a) arising from a use of ‘mempty’
Possible fix:
add (Monoid a) to the context of
the type signature for:
Fail.fail :: forall a. String -> Logger a
• In the expression: mempty
In an equation for ‘Fail.fail’: Fail.fail msg = mempty
In the instance declaration for ‘Fail.MonadFail Logger’
Why is there no instance for Monoid a
when it clearly says Monoid a => Monoid (Logger a)
? If my Logger a
type in instantiated as Monoid
why can't I use mempty in fail
definition? What am I missing?
Upvotes: 0
Views: 241
Reputation: 48601
Alexis King explains very well what the problem is. What are some potential solutions? One option is to include failure explicitly:
data Logger a = Logger
{ theResult :: Either String a
, theLog :: [String] }
Now you could write fail m = Logger {theResult = Left m, theLog = []}
.
Another option, which I think is more common, is to make your logger a monad transformer:
newtype Logger m a = Logger
{ runLogger :: m (a, [String]) }
instance MonadTrans Logger where
lift m = Logger $ do
a <- m
pure (a, [])
Now you can lift failure from the monad below:
instance MonadFail m => MonadFail (Logger m) where
fail = lift . fail
Rather than writing everything yourself, you can use Control.Monad.Trans.Writer.Strict
, or (in many/most cases better) Control.Monad.Trans.Writer.CPS
.
Upvotes: 3
Reputation: 43852
The error is not complaining about your Monoid
instance. That part is fine.
What it is complaining about is your use of that instance in the definition of your MonadFail
instance. When you write
fail msg = mempty
then GHC selects the Monoid (Logger a)
instance. But that instance has a constraint on it, namely Monoid a
. That means a
must be known to have a Monoid
instance at the place where mempty
is used, which in this case is inside your MonadFail
instance.
The root problem here is that fail
has a very general type signature:
fail :: forall m a. MonadFail m => String -> m a
Note that the a
here is universally quantified, which means the caller is allowed to instantiate it to any type they want. This means they could pick a type like Void
, which does not have a Monoid
instance. Therefore, your Logger
simply type cannot support a MonadFail
instance, since it would need to produce a value of a completely arbitrary type a
, and there is no way for it to do so.
Upvotes: 6