Search code examples
haskellmonadsghctype-systems

Why is FunctionalDependency needed for defining MonadReader?


I just managed to understand the definition of the class MonadReader

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

After reading the document of Functional Dependency in Haskell, now I can understand that | m -> r specifies that type variable r is uniquely decided by m. I think this requirement is reasonable based on the few typical instances of MonadReader I have seen so far (e.g. Reader), but it seems to me that we can still define instances like Reader even without this functional dependency clause.

My question is why we need functional dependency in the definition of MonadReader? Is this functionally necessary for defining MonadReader in a sense that MonadReader cannot be properly defined without it, or it is merely a restriction to limit the ways how MonadReader can be used so that the instances of MonadReader will all behave in a certain expected way?


Solution

  • 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.