This is interesting, as this has perplexed me also. Let's try to model it. I will probably not end up with the same thing this idiom is used for in Java.
If a type A
inherits from a type B
, in functional land that means that there is a function B -> A
. Do not worry about the difference between classes and interfaces at this point; there is little difference in the functional translation (an interface is just a record of functions). Let's do a non-recursive translation to get a feel for it:
interface Showable {
string show();
}
interface Describer<T extends Showable> { }
Translating to records of functions:
data Showable = Showable { show :: String }
data Describer t = Describer { showable :: t -> Showable }
If we forget about downcasting, then if we have some object in Java such that all we know about it is that it is a Showable
, then it corresponds to having a Showable
object in Haskell. On the surface, passing a Showable
and passing a string
feel like different things, but they are equivalent.
The extends Showable
constraint enters in that if we have a Describer t
then we know that t
"is" Showable
; i.e. there exists a function t -> Showable
.
makeDescriber :: (t -> Showable) -> Describer t
makeDescriber f = Describer { showable = f }
Now let's go to hammar's exmaple, incorporating polymorphism.
interface Number<A extends Number<A>> {
A add(A other);
}
translating to a record of functions
data Number a = Number {
add :: a -> a,
number :: a -> Number a
}
So now if we have a Number a
, then we know that a
"is" a Number a
; i.e. there is a function a -> Number a
.
Instances of the java interface Number
become functions on a type.
intNumber :: Integer -> Number Integer
intNumber x = Number { add = \y -> x + y, number = intNumber }
This function corresponds to a class Integer extends Number<Integer>
. If we have two integers x
and y
, we can add them using this "OO" style:
z :: Integer -> Integer -> Integer
z x y = intNumber x `add` y
And what about the generic function:
T Add< T extends Number<T> >(T x, T y) { return x.add(y); }
(hmm is that correct Java syntax? My experience of this style is from C#)
Remember the constraints become functions, so:
add' :: (t -> Number t) -> t -> t -> t
add' n x y = n x `add` y
Of course, in Haskell, we see how bundling an object together with the operations it supports is kind of convoluted, so we prefer to separate them:
data Num t = Num { add :: t -> t -> t }
add' :: Num t -> t -> t -> t
add' n x y = add n x y
And we instantiate the Num
dictionary with the actual operations, eg.
integerNum :: Num Integer
integerNum = Num { add = (+) }
Typeclasses are just a bit of syntactic sugar over this latter idea.
Maybe that helps? I just wanted to see how it would translate literally.