In general, no.
You can use tricks (as suggested in the other answers) that will make this work, but they do not provide all of the same guarantees that the Haskell typeclass does. Specifically, in Haskell, I could define a function like this:
doublyNegate :: Negatable t => t -> t
doublyNegate v = negate (negate v)
It is now known that the argument and return value of doublyNegate
are both t
. But the Java equivalent:
public <T extends Negatable<T>> T doublyNegate (Negatable<T> v)
{
return v.negate().negate();
}
doesn't, because Negatable<T>
could be implemented by another type:
public class X implements Negatable<SomeNegatableClass> {
public SomeNegatableClass negate () { return new SomeNegatableClass(); }
public static void main (String[] args) {
new X().negate().negate(); // results in a SomeNegatableClass, not an X
}
This isn't particularly serious for this application, but does cause trouble for other Haskell typeclasses, e.g. Equatable
. There is no way of implementing a Java Equatable
typeclass without using an additional object and sending an instance of that object around wherever we send values that need comparing, (e.g:
public interface Equatable<T> {
boolean equal (T a, T b);
}
public class MyClass
{
String str;
public static class MyClassEquatable implements Equatable<MyClass>
{
public boolean equal (MyClass a, MyClass b) {
return a.str.equals(b.str);
}
}
}
...
public <T> methodThatNeedsToEquateThings (T a, T b, Equatable<T> eq)
{
if (eq.equal (a, b)) { System.out.println ("they're equal!"); }
}
(In fact, this is exactly how Haskell implements type classes, but it hides the parameter passing from you so you don't need to figure out which implementation to send where)
Trying to do this with just plain Java interfaces leads to some counterintuitive results:
public interface Equatable<T extends Equatable<T>>
{
boolean equalTo (T other);
}
public MyClass implements Equatable<MyClass>
{
String str;
public boolean equalTo (MyClass other)
{
return str.equals(other.str);
}
}
public Another implements Equatable<MyClass>
{
public boolean equalTo (MyClass other)
{
return true;
}
}
....
MyClass a = ....;
Another b = ....;
if (b.equalTo(a))
assertTrue (a.equalTo(b));
....
You'd expect, due to the fact that equalTo
really ought to be defined symmetrically, that if the if
statement there compiles, the assertion would also compile, but it doesn't, because MyClass
isn't equatable with Another
even though the other way around is true. But with a Haskell Equatable
type class, we know that if areEqual a b
works, then areEqual b a
is also valid. [1]
Another limitation of interfaces versus type classes is that a type class can provide a means of creating a value which implements the type class without having an existing value (e.g. the return
operator for Monad
), whereas for an interface you must already have an object of the type in order to be able to invoke its methods.
You ask whether there is a name for this limitation, but I'm not aware of one. It's simply because type classes are actually different to object-oriented interfaces, despite their similarities, because they are implemented in this fundamentally different way: an object is a subtype of its interface, thus carries around a copy of the interface's methods directly without modifying their definition, while a type class is a separate list of functions each of which is customised by substituting type variables. There is no subtype relationship between a type and a type class that has an instance for the type (a Haskell Integer
isn't a subtype of Comparable
, for example: there simply exists a Comparable
instance that can be passed around whenever a function needs to be able to compare its parameters and those parameters happen to be Integers).
[1]: The Haskell ==
operator is actually implemented using a type class, Eq
... I haven't used this because operator overloading in Haskell can be confusing to people not familiar with reading Haskell code.