I would like to understand the key difference between parametric polymorphism such as polymorphism of generic classes/functions in the Java/Scala/C++ languages and "ad-hoc" polymorphism in the Haskell type system. I’m familiar with the first kind of languages, but I have never worked with the Haskell.
More precisely:
- How is type inference algorithm e.g. in Java different from the type inference in Haskell?
- Please, give me an example of the situation where something can be written in Java/Scala but cannot be written in Haskell(according to the modular features of these platforms too), and vice-versa.
As per the TAPL, §23.2:
So, if you consider successive stages of history, non-generic official Java (a.k.a pre-J2SE 5.0, bef. sept. 2004) had ad-hoc polymorphism – so you could overload a method – but not parametric polymorphism, so you couldn’t write a generic method. Afterwards you could do both, of course.
By comparison, since its very beginning in 1990, Haskell was parametrically polymorphic, meaning you could write:
where A and B are type variables can be instantiated to all types, without assumptions.
But there was no preexisting construct giving ad-hoc polymorphism, which intends to let you write functions that apply to several, but not all types. Type classes were implemented as a way of achieving this goal.
They let you describe a class (something akin to a Java interface), giving the type signature of the functions you want implemented for your generic type. Then you can register some (and hopefully, several) instances matching this class. In the meantime, you can write a generic method such as :
where the
Ordis the class that defines the function(_ ≤ _). When used,(between "abc" "d" "ghi")is resolved statically to select the right instance for strings (rather than e.g. integers) – exactly at the moment when (Java’s) method overloading would.You can do something similar in Java with bounded wildcards. But the key difference between Haskell and Java on that front is that only Haskell can do dictionary passing automatically: in both languages, given two instances of
Ord T, sayb0andb1, you can build a functionfthat takes those as arguments and produces the instance for the pair type(b0, b1), using, say, the lexicographic order. Say now that you are given(("hello", 2), ((3, "hi"), 5)). In Java you have to remember the instances forstringandint, and pass the correct instance (made of four applications off!) in order to applybetweento that object. Haskell can apply compositionality, and figure out how to build the correct instance given just the ground instances and thefconstructor (this extends to other constructors, of course) .Now, as far as type inference goes (and this should probably be a distinct question), for both languages it is incomplete, in the sense that you can always write an un-annotated program for which the compiler won’t be able to determine the type.
for Haskell, this is because it has impredicative (a.k.a. first-class) polymorphism, for which type inference is undecidable. Note that on that point, Java is limited to first-order polymorphism (something on which Scala expands).
for Java, this is because it supports contravariant subtyping.
But those languages mainly differ in the range of program statements to which type inference applies in practice, and in the importance given to the correctness of the type inference results.
AandBin the example above) can be only instantiated with non-polymorphic types (I’m simplifying, but this is essentially the ML-style polymorphism you can find in e.g. Ocaml.).The inference algorithm is essentially that of GJ, but with a somewhat kludgy addition of wildcards as an afterthought (Note that I am not up to date on the possible corrections made in J2SE 6.0, though). The large conceptual difference in approach is that Java’s inference is local, in the sense that the inferred type of an expression depends only on constraints generated from the type system and on the types of its sub-expressions, but not on the context.
Note that the party line regarding the incomplete & sometimes incorrect type inference is relatively laid back. As per the spec: