Let's say I have a function:
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
logResult = ...
In this function, if I get:
Right (Response a)
- I call toJSON
to log the result. Left MyError
- I log it as well. MyError
already has a ToJSON
instance defined.Now I want to write a helper function:
logError :: (MonadIO m) :: MyError -> m ()
logError err = logResult (Left err)
But GHC complains something along the following lines:
• Could not deduce (ToJSON a0) arising from a use of ‘logResult’
from the context: MonadIO m
bound by the type signature for:
logError :: forall (m :: * -> *).
MonadIO m =>
L.Logger
-> Wai.Request
-> MyError
-> m ()
...
...
The type variable ‘a0’ is ambiguous
I understand the error is because logResult
needs to guarantee that the a
in Response a
must have a ToJSON
instance defined. But in logError
I am explicitly passing Left MyError
. Shouldn't this disambiguate ?
Is there any way I can write the logError
helper function ?
PS: I have simplified the type signatures in the example. The error message has the gory details.
Why is this one function? If the behavior of this function splits so cleanly into two, then it should be two functions. That is, you've written one monolithic function and are trying to define a simpler function as a utility using it. Instead, write a simple function and write the monolithic function as a composition of it with another. The type is pretty much asking for it: Either a b -> c
is isomorphic to (a -> c, b -> c)
.
-- you may need to factor out some common utility stuff, too
logError :: (MonadIO m) :: MyError -> m ()
logResponse :: (MonadIO m, ToJSON a) => Response a -> m ()
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
logResult = either logError logResponse
logResult
still has its uses; if you get an Either MyError (Response a)
from some library, then logResult
can deal with it without much fuss. But, otherwise, you shouldn't be writing logResult (Left _)
or logResult (Right _)
very often; that essentially treats logResult . Left
and logResult . Right
as their own functions, which leads you back to actually writing them as separate functions.
But in
logError
I am explicitly passingLeft MyError
. Shouldn't this disambiguate?
No, it shouldn't. The end and beginning of the issue is that logResult
looks like this:
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
When you call it, the implementation doesn't matter one lick. The type says you need ToJSON a
—you need to provide ToJSON a
. That's it. If you know that you don't need ToJSON a
for Left
values, then you possess useful information that is not reflected in the type. You should add that information to the type, which, in this case, means splitting it into two. It would (IMO) actually be bad language design to allow what you were thinking of, because the halting problem should make it impossible to do correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With