Search code examples
haskellpolymorphism

Getting type error when calling a polymorphic function on two different types in a do block


Really not sure what is happening here. I'm not a Haskell newbee

I'm just trying to call a polymorphic function that I define on two different types and the compiler complains about the second function call because the type is different than the first time I called it.

main :: IO ()
main = do
    appData <- initializeAppData
    let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()
    log Log "My Game"
    log Debug appData

I'm getting the following error

/home/lianne/game/my-game/app/Main.hs:12:15: error:
• Couldn't match type ‘AppData’ with ‘[Char]’
  Expected: String
    Actual: AppData
• In the second argument of ‘log’, namely ‘appData’
  In a stmt of a 'do' block: log Debug appData
  In the expression:
    do appData <- initializeAppData
       let log = ...
       log Log "My Game"
       log Debug appData

If I switch the last two lines so it logs the appData before "My Game" then it says expected AppData and the actual type is String.

The type definition on the let I added for clarity and see if it would force the polymorphism. The getDebugPrint function has a type definition too.

I'm using stack and ghc 8.8.1

Is it because it is being defined in a do block?


Solution

  • It's the monomorphism restriction.

    For more detailed discussion of the monomorphism restriction in general, see the excellent standard question for it: What is the monomorphism restriction?

    But more or less it says: if a binding is defined without any syntactic parameters (regardless of whether it's type indicates it has parameters), and there's no explicit type signature for the binding, then any type variables with a type class constraint have to be resolved to a single type (that satisfies the constraint).

    let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()
    

    let log = ... is syntactically a simple variable binding, with no explicit names given to parameters. So that condition applies.

    But what about the "no explicit type signature" rule? You wrote right there that you wanted the type Show a => PrintLevel -> a -> IO (), so what gives?

    The way you have written it actually doesn't provide a type signature for the variable log. Rather you are providing a type annotation for the expression getDebugPrint appData, which is not the same thing. log could be given any type that is an instantiation of the right hand side's type, it doesn't have to be exactly the same.1

    So since you haven't given a type for the variable log itself, the compiler has to infer one. And in so doing it will apply the monomorphism restriction, which means it can't infer Show a => PrintLevel -> a -> IO (); since the type variable a is constrained by Show a it must be instantiated to a specific concrete type. It looks at the places where you have used log to pick one, so if you had only used it at a single type everything would have worked fine. But the first call log Log "My Game" requires it to pick String and the second requires it to pick AppData. Apparently it picked String and then complained about the other call not matching; I'm unsure whether it always picks based on the first usage or whether it's arbitrary, but it doesn't really matter.

    The simplest way to fix it is to put the type you wrote into a type signature for log rather than a type annotation for the expression (that's really what you're interested in fixing the type for anyway), like so:

    main :: IO ()
    main = do
        appData <- initializeAppData
        let log :: Show a => PrintLevel -> a -> IO ()
            log = getDebugPrint appData
        log Log "My Game"
        log Debug appData
    

    As for why you've never had this problem before, you should be able to see from my description above that several factors had to line up for this to be a problem:

    1. If you only use the binding at a single type, the compiler infers the correct monomorphic type based on usage anyway.
    2. If the type variable that is being used at multiple types doesn't have a class constraint, the monomorphism restriction doesn't apply.
    3. If you have explicit syntactic parameters in your binding, the monomorphism restriction doesn't apply.
    4. In GHCi the default is for the NoMonomorphismRestriction extension to be active (since GHC 8 or so, IIRC), which disables the monomorphism restriction. So anything you try in the interpreter isn't going to have this issue.

    So it's quite plausible this is simply the first time that this coding pattern has triggered all of the conditions at once.


    1 If you think that's dumb, consider this:

    x :: Int
    x = 1
    

    That wouldn't be valid if you couldn't define a specifically-typed variable with a more general expression on the right hand side, as 1 naturally has type Num a => a.