Why is FunctionalDependency needed for defining MonadReader?

It is needed to make type inference work in a way which is more convenient to the user.

For example, without the fundep this would not compile:

action :: ReaderT Int IO ()
action = do
  x <- ask
  liftIO $ print x

To make the above compile we would need to write

action :: ReadertT Int IO ()
action = do
  x <- ask :: ReadertT Int IO Int
  liftIO $ print x

This is because, without the fundep, the compiler can not infer that x is an Int. After all a monad ReadertT Int IO might have multiple instances

instance MonadReader Int (ReaderT Int IO) where
   ask = ReaderT (\i -> return i)
instance MonadReader Bool (ReaderT Int IO) where
   ask = ReaderT (\i -> return (i != 0))
instance MonadReader String (ReaderT Int IO) where
   ask = ReaderT (\i -> return (show i))
-- etc.

so the programmer must provide some annotation which forces x :: Int, or the code is ambiguous.


I think the source of confusion is that in the definition of

class Monad m => MonadReader r m | m -> r where
  {- ... -}

It is implicitly asumed that m contains r itself (for common instances). Let me use a lighter definiton of Reader as

newtype Reader r a = Reader {runReader :: r -> a}

When the r parameter is chosen you can easely define a monad instance for Reader r. That means that in the type class definition m should be substitute for Reader r. So take a look at how the expresion ends up being:

instance MonadReader r (Reader r) where -- hey!! r is duplicated now
  {- ... -}                             -- The functional dependecy becomes Reader r -> r which makes sense

But why do we need this?. Look at the definition of ask within the MonadReader class.

class Monad m => MonadReader r m | m -> r where
  ask :: m r -- r and m are polymorphic here
  {- ... -}

Without the fun-dep nothing could stop me for defining ask in a way to return a different type as the state. Even more, I could define many instances of monad reader for my type. As an example, this would be valid definitions without func-dep

instance MonadReader Bool (Reader r) where
--                   ^^^^         ^
--                   |            |- This is state type in the user defined newtype 
--                   |- this is the state type in the type class definition
  ask :: Reader r Bool
  ask = Reader (\_ -> True) -- the function that returns True constantly
  {- ... -}                             
instance MonadReader String (Reader r) where
--                   ^^^^^^         ^
--                   |              |- This is read-state type in the user defined newtype 
--                   |- this is the read-state type in the type class definition
  ask :: Reader r String
  ask = Reader (\_ -> "ThisIsBroken") -- the function that returns "ThisIsBroken" constantly
  {- ... -}                             

So if I had a value val :: ReaderT Int IO Double what would be the result of ask. We'd need to specify a type signature as below

val :: Reader Int Double
val = do
  r <- ask :: Reader Int String
  liftIO $ putStrLn r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
"ThisIsBroken"
1.0

val :: Reader Int Double
val = do
  r <- ask :: Reader Int Bool
  liftIO $ print r   -- Just imagine you can use liftIO
  return 1.0

> val `runReader` 1
True
1.0

Aside from being senseless, it is unconvinient to be specifiying the type over and over.

As a conclusion using the actual definition of ReaderT. When you have something like val :: ReaderT String IO Int the functional dependency says Such a type might have only one single instance of MonadReader typeclass which is defined to be the one that uses String as r


This is not really an answer, but it's much too long for a comment. You are correct that it's possible to define the MonadReader class without a fundep. In particular, the type signature of each method determines every class parameter. It would be quite possible to define a finer hierarchy.

class MonadReaderA r m where
  askA :: m r
  askA = readerA id

  readerA :: (r -> a) -> m a
  readerA f = f <$> askA

-- This effect is somewhat different in
-- character and requires special lifting.
class MonadReaderA r m => MonadReaderB r m where
  localB :: (r -> r) -> m a -> m a

class MonadReaderB r m
  => MonadReader r m | m -> r

ask :: MonadReader r m => m r
ask = askA

reader
  :: MonadReader r m
  => (r -> a) -> m a
reader = readerA

local
  :: MonadReader r m
  => (r -> r) -> m a -> m a
local = localB

The main problem with this approach is that users have to write a bunch of instances.