Created
August 7, 2012 22:48
-
-
Save seanparsons/3290236 to your computer and use it in GitHub Desktop.
Typeclasses: Saving you code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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