In this question, untagged unions are described as a form of subtyping.
Type classes are also a form of subtyping.
Are they conceptually equivalent? If they are, how would I implement these in Haskell?
Type classes are also a form of subtyping.
They aren't. For the sake of illustration, let's return to the TypeScript examples I alluded to in that question:
If we have a value that has a union type, we can only access members that are common to all types in the union.
interface Bird { fly(); layEggs(); } interface Fish { swim(); layEggs(); } function getSmallPet(): Fish | Bird { // ... } let pet = getSmallPet(); pet.layEggs(); // okay pet.swim(); // errors
Here, the return type of getSmallPet
is neither Fish
nor Bird
, but a supertype of both that has as members the members common to both Fish
and Bird
. A Fish
is also a Fish | Bird
, and so is a Bird
.
What happens with type classes is quite different:
foo :: Num a => a -> a
foo x = (x * x) + x
While both foo (3 :: Integer)
and foo (7.7 :: Double)
work, that does not mean there is a supertype corresponding to Num
that (3 :: Integer)
and (7.7 :: Double)
also have. Rather, all that Num a => a -> a
says is that your choice of a
should have an instance of Num
(and it is worth emphasising that Num
is not a type), so that there are suitable implementations of (*)
and (+)
for your chosen type. Unlike OOP methods, (*)
and (+)
do not belong to any particular type, and so it is not necessary to introduce a supertype in order to use them with both Integer
and Double
.