Search code examples
haskellmonad-transformersnewtype

Implicit type coercion?


I don't understand why this code typechecks:

error1 :: ErrorT String (ReaderT Int IO) Int
error1 = asks id

fyi, the asks has this type:

asks :: Monad m => (r -> a) -> ReaderT r m a

On the other hand, I'm able to understand, that this code typechecks:

reader1 :: ReaderT Int IO Int
reader1 = asks id

id has type a -> a and there is an instance of Monad for IO, so the compiler can infer the type. That's clear for me.

The ErrorT is newtype and haskell spec states, (in the section about newtypes):

... it creates a distinct type that must be explicitly coerced to or from the original type ...

According to my interpretation, I should be able to get the same type as in error1 only explicitly, with some coercion similar to this:

reader2 :: ReaderT Int IO (Either String Int)
reader2 = fmap (\i -> Right i) reader1

error2 :: ErrorT String (ReaderT Int IO) Int
error2 = ErrorT reader2

But, apparently, since the error1 typechecks just fine, there is some knowledge hidden from me. Can You help uncovering it for me?

The imports needed for running the example code:

import Control.Monad.Error (ErrorT(ErrorT))
import Control.Monad.Reader (ReaderT, asks)

Solution

  • The function asks is exported by two related modules with slightly different types. The version from Control.Monad.Trans.Reader (part of the transformers package), has the type given in the question:

    asks :: Monad m => (r -> a) -> ReaderT r m a
    

    However, the version used seems to be the one in the mtl package, from the Control.Monad.Reader module, which has the following, more general, type:

    asks :: MonadReader r m => (r -> a) -> m a
    

    So the example definition

    error1 :: ErrorT String (ReaderT Int IO) Int
    error1 = asks id
    

    means that

    MonadReader Int (ErrorT String (ReaderT Int IO))
    

    must hold.

    Also defined by mtl are the following instances for MonadReader:

    instance Monad m => MonadReader r (ReaderT r m)
    instance (Error e, MonadReader r m) => MonadReader r (ErrorT e m)
    

    With these, the constraint above reduces to

    (Error String, Monad IO)
    

    which both hold as well.