Pattern matching vs Type class

Hi all,

I have these 2 implementations doing the same thing. I have the feeling that using type classes is the more recommended “pattern”. Can someone explain to me why that is? Because to a beginner like me pattern matching is much more straight forward

Pattern Matching

  trait Animal
  final case class Dog() extends Animal
  final case class Cat() extends Animal

  def talk(a: Animal): String =
    a match {
      case Dog() => "Woof"
      case Cat() => "Meow"
    }
  println(talk(Cat()))
  println(talk(Dog()))

Type class

  trait Talkable[A] {
    def talk(a: A): String
  }
  object Talkable {
    implicit object Dog extends Talkable[Dog] {
      override def talk(a: Dog): String = "Woof"
    }
    implicit object Cat extends Talkable[Cat] {
      override def talk(a: Cat): String = "Meow"
    }
  }

  def speak[A: Talkable](a: A)(implicit instance: Talkable[A]) = {
    instance.talk(a)
  }

  println(speak(Cat()))
  println(speak(Dog()))
1 Like

Do you expect the “talk” API to be strictly limited to the Animal hierarchy? Then pattern matching (or alternatively the OO approach with an abstract method in the super trait and implementations in the concrete classes) should be fine. If there’s any chance that the “talk” concept might become applicable to arbitrary types, the type class approach is a safer bet.

Note that even for the type class approach, it’d be quite common to declare Talkable[Animal] and implement the specializations for Cat and Dog via pattern matching, again.

1 Like

It also depends if Animal is a sealed trait. If more subclasses can be added—elsewhere in the codebase or even in a different codebase—then you probably don’t want to pattern match.

1 Like

I missed that out. I meant for it to be ADT

I once wrote about the different forms of polymorphism in Scala and their trade-offs.

I did not included pattern matching, but you may think of it as similar to subtyping, just with a sealed family of types.

Hope you find it interesting as well as helpful: https://gist.github.com/BalmungSan/c19557030181c0dc36533f3de7d7abf4

super awesome thanks

A type class is useful if you want to retrofit a new behaviour onto existing types that you can’t control. But if you do control all the types to begin with, it’s overkill. And pattern-matching is not as ergonomic as just putting a method in the trait, because you would have to think about where to import the method from. I recommend just putting it in the trait, then you get the method as an instance member:

trait Animal extends Product with Serializable {
  def talk: String
}

object Animal {
  object Dog extends Animal {
    def talk: String = "Woof"
  }

  object Cat extends Animal {
    def talk: String = "Meow"
  }
}

...

println(Animal.Cat.talk)
println(Animal.Dog.talk)