Scala Language Type Classes Add type class functions to types


Example

Scala's implementation of type classes is rather verbose. One way to reduce the verbosity is to introduce so-called "Operation Classes". These classes will automatically wrap a variable/value when they are imported to extend functionality.

To illustrate this, let us first create a simple type class:

// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
  def add(x: A, y: A): A
}

Next we will implement the trait (instantiate the type class):

object Instances {

  // Instance for Int
  // Also called evidence object, meaning that this object saw that Int learned how to be added
  implicit object addableInt extends Addable[Int] {
    def add(x: Int, y: Int): Int = x + y
  }

  // Instance for String
  implicit object addableString extends Addable[String] {
    def add(x: String, y: String): String = x + y
  }

}

At the moment it would be very cumbersome to use our Addable instances:

import Instances._
val three = addableInt.add(1,2)

We would rather just write write 1.add(2). Therefore we'll create an "Operation Class" (also called an "Ops Class") that will always wrap over a type that implements Addable.

object Ops {
  implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
    def add(other: A): A = A.add(self, other)
  }
}

Now we can use our new function add as if it was part of Int and String:

object Main {

  import Instances._ // import evidence objects into this scope
  import Ops._       // import the wrappers

  def main(args: Array[String]): Unit = {

    println(1.add(5))
    println("mag".add("net"))
    // println(1.add(3.141)) // Fails because we didn't create an instance for Double

  }
}

"Ops" classes can be created automatically by macros in simulacrum library:

import simulacrum._

@typeclass trait Addable[A] {
  @op("|+|") def add(x: A, y: A): A
}