Haskell does type inference in two "directions" at once.
- Bottom-up: by looking at all the parts of an expression, what should be the type of the whole expression
- Top-down: by looking at where this expression is used, what type should it have in order to fit into that context
If there is any place where the two do not agree, there will be a type error reported.
GHC's error messages call type it's worked out by "bottom-up" analysis the "actual type", and the type it's worked out by "top-down" analysis the "expected type". But don't be confused by this terminology; it's not that the "expected type" any more correct than the actual type. It is not telling you that the type should be the expected type but actually is the actual type. It's just telling you that two different lines of analysis lead to a contradiction about what the type of the expression is.
As such, it's kind of absurd to say that one of these error messages is "right" and the other is "wrong". They are both just telling you "these 2 things don't match, and they should". The compiler has no way of knowing which of the two types the expression "should" have if it was really correct; frequently neither of the two will work, no matter what else you change.
In particular, let's look at the type inference going on in the message you don't like:
bugTI.hs:7:29: error:
• Couldn't match type ‘Int’ with ‘[Char]’
Expected: String
Actual: Int
• In the second argument of ‘(+)’, namely ‘int’
In the expression: str * 10 + int
In an equation for ‘num’: num = str * 10 + int
|
7 | where num = str * 10 + int
| ^^^
And for reference the code is:
bug :: String -> Int -> Int
bug str int = num
where num = str * 10 + int
So to report a type error at the position indicated, we need to have both an "actual" (bottom-up) and "expected" (top-down) type for this occurence of int. The bottom-up type is easy; we know it's Int because the expression int only consists of a single variable reference, and that variable was declared to have type Int.
The expected type for int here depends on the context. int is occurring as the right operand to +, with the left operand being str * 10. + has type Num a => a -> a -> a. So to know what type we "expect" for int we need to know what type str * 10 is inferred as.
* has type Num a => a -> a -> a, so str and 10 must have the same type, and the overall expression str * 10 also has that type. 10 can be any type with a Num instance, but str already has a fixed type from the type signature; it is String (AKA [Char]).
Putting that all together, we now know that
- the expressions
str, 10, int, str * 10, and str * 10 + int all must have the same type
str has type String
int has type Int
These 3 facts cannot all be true. So we need to emit an error message.
This is the point where you go wrong. You have knowledge of your conceptual ideas about Num - that we do arithmetic on numbers and String isn't a number - so you say clearly we should "expect" Int for all of those, and the error that should be reported is that str has actual type String and expected type Int. But the compiler cannot possible know about your conceptual ideas about what Num is "for". Without that, your interpretation of the code is not any more valid than doing the opposite: saying that all of those expressions are expected to have type str and it's int that doesn't match! Exactly because there is no consistent typing here, there are multiple ways to get "as close as possible" and then report an error. It is essentially arbitrary which form the error takes in the error message1; neither is better than the other. So neither one can be the "wrong" error message.2
Without knowing the really fine details of the type checker implementation, I would hazard a guess based on this that it simply encountered the str * 10 first and "committed" to String at that point; it knows enough even without looking ahead to know that choosing String as the type of both 10 and also of str * 10 is the only possible chance it has of accepting this code, so there is "nothing to lose" by doing that. And then when it later also finds a requirement forcing that same type to also be Int, it reports an error.3
Now you noted in comments that there is also another error under this interpretation that str, 10, and int are all expected to be String. That is the error that this choice will require an instance Num String, and there is no such instance visible to the compiler. It doesn't report that error because it is "behind" this one; the compiler needs to go looking for an Num instance that matches whatever type is chosen for str, 10, and int, but it doesn't make sense to go looking for that instance until after we've figured out what instance we need! That requires pinning down the type involved, and having realised that there is no consistent type it's pointless even trying to resolve the Num constraint. This is an error whether or not Num Int or Num String or Num <anything-else> hold true.
1 As evidence for my claim that which way around the error is reported is arbitrary, I don't even get the same error messages as you when I tried this. I don't know whether it's just a different version of GHC, or whether it would change sometimes even for the same version. But I get an error about int not being String for both bug and works:
foo.hs:6:16: error:
• Couldn't match type ‘[Char]’ with ‘Int’
Expected: Int
Actual: String
• In the expression: num
In an equation for ‘bug’:
bug str int
= num
where
num = str * 10 + int
|
6 | bug str int = num
| ^^^
foo.hs:7:29: error:
• Couldn't match type ‘Int’ with ‘[Char]’
Expected: String
Actual: Int
• In the second argument of ‘(+)’, namely ‘int’
In the expression: str * 10 + int
In an equation for ‘num’: num = str * 10 + int
|
7 | where num = str * 10 + int
| ^^^
foo.hs:11:17: error:
• Couldn't match type ‘[Char]’ with ‘Int’
Expected: Int
Actual: String
• In the expression: str * 10 + int
In an equation for ‘works’: works str int = str * 10 + int
|
11 | works str int = str * 10 + int
| ^^^^^^^^^^^^^^^^
foo.hs:11:30: error:
• Couldn't match type ‘Int’ with ‘[Char]’
Expected: String
Actual: Int
• In the second argument of ‘(+)’, namely ‘int’
In the expression: str * 10 + int
In an equation for ‘works’: works str int = str * 10 + int
|
11 | works str int = str * 10 + int
|
2 In fact these aren't even the only two possibilities. Absent wider context, it's just as likely that int and str are both "correct", and the bug is that the wrong functions were used on them (i.e. it's + and * that are wrong). GHC could quite reasonably report that this occurrence of + has expected type String -> Int -> Int and actual type Int -> Int -> Int, or something similar; that is also another equivalent way of reporting the same fundamental inconsistency that is present in your code.
I don't believe it will (often? ever?) actually report errors in that form. As far as I can recall it reports errors about arguments not fitting the function, rather than the function not fitting the arguments. I don't actually know why, whether it's a deliberate decision (for simpler error messages?) or just an accident of the type checker code works. But it's always a possibility for the real mistake that the programmer made, so you need to read errors with this sort of possible translation in mind.
3 And I suppose that this doesn't happen in the works case because you don't have the intermediate num definition that can be inferred its own type; instead the str * 10 + int expression occurrs in a context where it is known that it should result in Int, because it is directly the return value of the works function that is declared to return Int. Although as I noted in my previous footnote, this doesn't happen when I run it. But that's fine. There is no one correct order in which GHC is required to consider the pieces of information available to it before it notices the inconsistency.