haskelltype-theorysubtyping

Is allowing untagged unions equivalent to allowing type classes?


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?


Solution

  • 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.