Skip to content

Instantly share code, notes, and snippets.

@seanparsons
Created August 7, 2012 22:48
Show Gist options
  • Save seanparsons/3290236 to your computer and use it in GitHub Desktop.
Save seanparsons/3290236 to your computer and use it in GitHub Desktop.
Typeclasses: Saving you code.
// An example of typeclasses brought to you by addition and multiplication.
// In a traditional OO design, methods are baked into a particular type, so in this example:
println("2 times 2 is: %s".format(2 * 2))
// The method "*" is part of the Int type.
// This is perfectly fine if all the possible functionality for a given type is known
// at the point when that particular type was compiled. But what do we do if a feature
// that wasn't expected when a library was built or even if the library was built by
// someone else?
//
// We need to write that functionality outside of the original type, creating a typeclass.
// For those with knowledge of Java, compare the types Comparable and Comparator, to see one
// way this can be achieved.
//
// In Scala we can use implicits to achieve this in a much cleaner and less manual way.
//
// If we wanted the methods add and multiply to Int we write something like this:
// The typeclasses that relate to the concept of multiplying or adding:
trait Add[T] {
def add(first: T, second: T): T
def subtract(first: T, second: T): T
}
trait Multiply[T] {
def multiply(first: T, second: T): T
}
// The typeclass instances for Int.
implicit val intAdd = new Add[Int] {
def add(first: Int, second: Int) = first + second
def subtract(first: Int, second: Int) = first - second
}
implicit val intMultiply = new Multiply[Int] {
def multiply(first: Int, second: Int) = first * second
}
// This is a somewhat synthetic construct that creates a wrapper around the original value
// providing additional methods to it.
case class RichNumber[T](value: T) {
def add(other: T)(implicit numberAdd: Add[T]): T = numberAdd.add(value, other)
def subtract(other: T)(implicit numberAdd: Add[T]): T = numberAdd.subtract(value, other)
def multiply(other: T)(implicit numberMultiply: Multiply[T]): T = numberMultiply.multiply(value, other)
}
// The parameter group marked as implicit can be filled in automatically by the compiler on your
// behalf if exactly one expression of the appropriate type is in scope and marked as implicit.
implicit def valueToRichNumber[T](value: T): RichNumber[T] = new RichNumber(value)
// The "implicit def" provides a conversion that the compiler will add in automatically for
// us should we require it, this means we can write "1.add(2)".
// Now the following can be written:
println("1.add(2) is: %s".format(1.add(2))) // Could be written as "1 add 2".
println("1.multiply(2) is: %s".format(1.multiply(2))) // Could be written as "1 multiply 2".
// It's worth noting that this is completely type safe attempting the following would
// result in a compile error:
// println("true.add(true) is: %s".format(true.add(true)))
// Even though it's valid for the RichNumber type to wrap around anything including
// Boolean, the contract of the add method requires an instance of Add[Boolean] in this case.
// Where this real power comes in is that typeclasses can be used as building blocks,
// even to the point of creating typeclasses from other typeclasses.
// Since multiplication can be expressed in terms of addition we can do the following:
// Create another typeclass as more info is needed:
trait One[T] {
def one: T
def greaterThanOrEqualToOne(value: T): Boolean
}
// Reimplement Multiply in terms of Add and One.
implicit def numberMultiplyFromAdd[T](implicit numberAdd: Add[T], one: One[T]): Multiply[T] = new Multiply[T] {
def multiply(first: T, second: T): T = {
def addAnother(number: T, howMany: T): T = {
if (one.greaterThanOrEqualToOne(howMany)) {
addAnother(number.add(first), howMany.subtract(one.one))
} else {
number
}
}
addAnother(first, second)
}
}
// Create implementations of the typeclasses for Long.
implicit val longAdd = new Add[Long] {
def add(first: Long, second: Long) = first + second
def subtract(first: Long, second: Long) = first - second
}
implicit val longOne = new One[Long] {
val one = 1l
def greaterThanOrEqualToOne(value: Long) = value > one
}
// As a result of this, for any type T where Add[T] and One[T] are present, a Multiply[T]
// will be available without the need to specifically define one:
println("25l.multiply(1000000l) is: %s".format(25l.multiply(1000000l)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment