Search code examples
scalainheritancefunctional-programmingtypeclassscala-cats

Why does Scala Cats use typeclasses instead of inheritance?


What's the point of using a typeclass over inheritance?

This is a function that uses the Monad typeclass by a context bound:

def f[A : Monad](x:A) = ??? 

( Yeah, we get flatMap method now)

This, however, uses inheritance with a subtype bound:

def f[A <: Monad](x:A) = ???  
f(x) // where x is a CatsList which implements Monad trait.

( We also get flatMap method now.)

Don't both achieve the same thing?


Solution

  • Typeclasses are more flexible. In particular, it's possible to retrofit a typeclass to an existing type easily. To do this with inheritance, you would need to use the adapter pattern, which can get cumbersome when you have multiple traits.

    For example, suppose you had two libraries which added the traits Measurable and Functor, respectively, using inheritance:

    trait Measurable {
      def length: Int
    }
    
    trait Functor[A] {
      def map[B](f: A => B): Functor[B]
    }
    

    The first library helpfully defined an adapter for List => Measurable:

    class ListIsMeasurable(ls: List[_]) extends Measurable {
      def length = ls.length
    }
    

    And the second library did the same for List => Functor. Now we want to write a function that takes things that have both length and map methods:

    def foo(x: Measurable with Functor) = ???
    

    Of course, we should hope to be able to pass it a List. However, our adapters are useless here, and we have to write yet another adapter to make List conform to Measurable with Functor. In general, if you have n interfaces that might apply to List, you have 2^n possible adapters. On the other hand, if we had used typeclasses, there would have been no need for the extra boilerplate of a third typeclass instance:

    def foo[A : Measurable : Functor](a: A) = ???