1
votes

While building a monad stack with monad transformers to write a library, I hit a question about the behavior of it.

The following code won't pass the type checker:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Foo (FooM, runFooM, foo) where

import Control.Applicative
import Control.Monad.Reader

newtype FooM m a = FooM { runFooM :: ReaderT Int m a }
  deriving (Functor, Applicative, Monad, MonadReader Int)

foo :: FooM m Int
foo = do
  x <- ask
  return x

The error is:

$ ghc foo.hs
[1 of 1] Compiling Foo              ( foo.hs, foo.o )

foo.hs:12:3:
    No instance for (Monad m) arising from a do statement
    Possible fix:
      add (Monad m) to the context of
        the type signature for foo :: FooM m Int
    In a stmt of a 'do' block: x <- ask
    In the expression:
      do { x <- ask;
           return x }
    In an equation for ‘foo’:
        foo
          = do { x <- ask;
                 return x }

The fix is easy as GHC suggests, just adds Monad constraint to the foo function:

foo :: Monad m => FooM m Int
foo = do
  x <- ask
  return x

But here, the foo function only asks the FooM value to give its Int value and it is already an (automatically derived) MonadReader instance. So I think Monad constraint is not required to m.

I guess this relates to the implementation of the monad transformers (I use mlt==2.2.1), but I cannot figure out the exact reason. I may miss something obvious though. Could you explain why this doesn't pass the checker?

Thanks.

3

3 Answers

10
votes

It's because the Monad instance for ReaderT is defined as

instance Monad m => Monad (ReaderT r m)

i.e. the type ReaderT r m is an instance of Monad only if the inne rm is an instance of Monad. That's why you cannot have an unconstrained m when using the Monad instance of ReaderT (which your FooM type is using via the deriving mechanism).

5
votes

returns type is Monad m => a -> m a, hence the need for the constraint.

5
votes

By the monad laws, foo ≡ ask, which will indeed work without the Monad constraint. But if you don't require Monad, then GHC can hardly make simplifications based on these laws, can it? Certainly not before type checking the code. And what you wrote is syntactic sugar for

foo = ask >>= \x -> return x

which requires both (>>=) :: Monad (FooM m) => FooM m Int -> (Int->FooM m Int) -> FooM m Int and return :: Monad (FooM m) => Int->FooM m Int.

Again, the >>= return does nothing whatsoever for a correct monad, but for a non-monad it isn't even defined and can thus not just be ignored.