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?
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:
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
.