Why is Scala's type inference not as powerful as Haskell's?
The main reason is that the type system of Scala allows sub-typing, which the Hindley-Milner type inference algorithm does not support.
Haskell does not have sub-typing, so the algorithm works much better there, although many popular type system extensions supported by GHC cause type inference to fail again, forcing you to provide explicit type signatures for some expressions.
In the end, it's a trade-off between the power of the type system and the amount of type inference that can be done. Scala and Haskell have simply made different trade-offs.
I guess the main reasons have already been given, but I find this quote by Scala's creator Martin Odersky to be particularly informative,
The reason Scala does not have Hindley/Milner type inference is that it is very difficult to combine with features such as overloading (the ad-hoc variant, not type classes), record selection, and subtyping. I’m not saying impossible — there exist a number of extensions that incorporate these features; in fact I have been guitly of some of them myself. I’m just saying it’s very difficult to make this work well in practice, where one needs to have small type expressions, and good error messages. It’s not a shut case either — many researchers are working on pushing the boundaries here (look for instance at Remy’s MLF). But right now it is a tradeoff of better type inferencing vs better support for these features. You can make the tradeoff both ways. The fact that we wanted to integrate with Java tipped the scales in favor of subtyping and away from Hindley/Milner.
Source: comment under post Universal Type Inference is a Bad Thing.
hammar gave the biggest reason. Here are two others:
- Scala uses types to resolve names
Consider
def foo(x) = x.a + x.b
How could Scala possibly infer the type of the argument? Should it look for every class with an a
and b
field? What if there are more than 1? In Haskell
foo x = (a x) + (b x)
record names are unique, which presents its own problems, but means you can always infer what kind of record is being referred to.
- For your example:
case
expressions in Scala can be heterogeneous
In Scala the type of the object being matched can be used either as part of the match, or to decide how matching should be done. So even if all the constructors in the case
are for List
, you might want to pass something other than a list to it, and have it fail.
Another thing that doesn't play well with Hindley-Milner type inference is method overloading, and related features like default arguments and varargs. That's the reason why it is so hard to write things like zipWithN in Haskell (which is trivial in Scala).