8

I'm learning about paradigms of functional languages. So I've read that while Subtype polymorphism is typical for OO languages, Parametric polymorphism is typical for functional languages.

But I wonder if there is Subtype polymorphism in functional languages. The functional language I know best is Haskell, and I know there are typeclasses in Haskell.

I always thought of typeclasses to be somewhat similiar to interfaces, and interfaces are Subtype polymorphism. E.g. here:

data Foo a = Bar a | Baz String
deriving instance Eq a => Eq (Foo a)

Eq is a typeclass that defines the (==) operator. That's basically like a Foo class implementing a Eq interface.

So what I'm asking is: Is this considered Subtyping (or Subtype polymorphism)?

3
  • My path to understanding the practical difference was to think about the map function. With typeclasses, it can act on several different collection types and return the original collection type. With OO interfaces, it's not possible to do this in a type-safe way. Try to implement this in Java or C# and you'll see. Commented Jul 7, 2016 at 11:13
  • A good way of understanding might be to take a look at Scala, and how typeclasses are encoded as implicit objects and implicit arguments. Scala is a deeply and thoroughly object-oriented language, much more so than Java or C♯, and seeing how typeclasses are represented there in an OO way really highlights the difference to OO-style subtype polymorphism. Commented Jul 7, 2016 at 21:58
  • See: Difference between OOP interfaces and FP type classes, which has what I judge to be the most thorough answer I've seen.
    – sacundim
    Commented Jul 17, 2016 at 20:09

3 Answers 3

3

To answer your question more explicitly: no, this is not subtype polymorphism, and the reason is simply this: each type that has an instance of a typeclass is distinct. Each has different functions with (potentially) different signatures, for example if we have the following types:

data First = First String
             deriving Eq
data Second = Second Int
              deriving Eq

these end up with two entirely distinct functions:

(==) :: First -> First -> Bool
(==) :: Second -> Second -> Bool

To be subtypes of the same type, they would need to have compatible type signatures, i.e. you would need to be able to use either in exactly the same situations, but you can't. If you have a value with an Eq instance, you can't arbitrarily compare it to another value unless you know both have the same type.

Type classes are effectively a way for doing two things:

  • Specifying predicates on type parameters
  • Allowing sharing of function/operator names between types by specifying a uniform way of determining how to handle the name

While they have similarities between interfaces in common usage, they are very different in subtle but very important ways.

5

Typeclasses instances are effectively implicit parameters containing a dictionary of functions. This makes them different from interfaces in a few ways.

One is that you don't need an actual instance on which to invoke operations, and resolution can happen at the type level instead of the value level. The Read class is an example:

class Read a where
  read :: String -> a

another difference is that the class approach can enforce uniformity between parameter types, while interfaces hide the type of one of the parameters (the receiver). Given the Eq example in your question, the interface approach would allow you to define a class like:

class Dog : IEquatable<Cat> {
    bool Equals(Cat c) { return false }
}

which you cannot do with the typeclass approach since the type a only appears once in the definition. Many OO language then define another interface such as

interface IEqualityComparer<T> {
    bool Equals(T first, T second);
}

which is similar to the typeclass approach, except instances of this interface must be passed explicitly into each operation, while typeclass instances are passed implicitly.

3

The biggest difference is that type classes are open, and interfaces are closed.

What I mean is that if you have some 3rd party type, if it doesn't implement an interface then you're out of luck. You get to extend it or adapt it into a new type which can then implement the interface you want.

If you have some 3rd party type that doesn't implement a type class, that's fine. You just go in and specify the type class implementation for that 3rd party type.

This allows a lot of flexibility when designing your code and gluing things together.

1
  • On the other hand, though, Go's interfaces are open but have the same semantics as more typical OO interfaces. Typeclasses, on the other hand, work in a subtly different way that can be quite important in a few very critical places (see Eric Lippert on why the Monad typeclass can't be implemented in Java for an explanation of why this is important).
    – Jules
    Commented Jul 7, 2016 at 12:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.