Search code examples
haskelltypechecking

GHC: Why does type ambiguity go away when using let


I have a type class

class (Monad f) => Test f where
  test :: () -> f ()

and an instance

instance Test (ErrorT String (Identity)) where
  test pat = return pat

If I run a monad stack referring to this instance, GHC can't find out what monad I am talking about (in a do block of the Either String monad):

rhs' <- runIdentity $ runErrorT $ test rhs

yields the error message:

Ambiguous type variable `f0' in the constraint:
  (Test f0) arising from a use of `test'
  ...

But if I bind the part test rhs to a variable:

let action = test rhs
rhs' <- runIdentity $ runErrorT $ action

it works, even though the variable is used nowhere else so nothing new can be inferred about it.

How is this possible, if I have added no information for the type checker to use? Why can't it figure out the type of the equivalent first formulation? Or are the two formulations not equivalent? What part of the Haskell type checker (or desugaring rules?) did I not understand here?

I am using the extensions MultiParamTypeClasses, FlexibleInstances and ScopedTypeVariables

edit: I simplified the example so the strange issue occurs without needing the rest of my code (and with a shorter monad stack), but now it looks nonsensical. The full context of the statement is:

doStuff :: (Map Int ()) -> Either String (Map Int ())
doStuff g = run (snd . head . Map.toList $ g) g where
 run :: () -> Map Int () -> Either String (Map Int ())
 run rhs g = do
  let action = test rhs 
  rhs' <- runIdentity $ runErrorT $  test rhs -- or: action
  return g

Solution

  • The code

    doStuff :: (Map Int ()) -> Either String (Map Int ())
    doStuff g = run (snd . head . Map.toList $ g) g where
     run :: () -> Map Int () -> Either String (Map Int ())
     run rhs g = do
      rhs' <- runIdentity $ runErrorT $  test rhs
      return g
    

    or

    doStuff :: (Map Int ()) -> Either String (Map Int ())
    doStuff g = run (snd . head . Map.toList $ g) g where
     run :: () -> Map Int () -> Either String (Map Int ())
     run rhs g = do
      let action = test rhs
      rhs' <- runIdentity $ runErrorT $ action
      return g
    

    should type check without issue. The problem is

    doStuff :: (Map Int ()) -> Either String (Map Int ())
    doStuff g = run (snd . head . Map.toList $ g) g where
     run :: () -> Map Int () -> Either String (Map Int ())
     run rhs g = do
      let action = test rhs
      rhs' <- runIdentity $ runErrorT $ action
      return g
    

    the reason for this is that you seem to have MonoLocalBinds or the Monomorphism Restriction enabled which prevents generalizations of the binding to action unless the type is known.