Scala snippets 4: Pimp my library pattern with type classes.
I wanted to write an article on the fun parts of scalaz, but thought it would be best to first look a bit closer at the type classes system provided by scala. So in this snippet we’ll explore a small part of how type classes work and can help you in writing more generic code.
More snippets can be found here:
- Scala snippets 1: Folding
- Scala snippets 2: List symbol magic
- Scala snippets 3: Lists together with Map, flatmap, zip and reduce
- Scala snippets 4: Pimp my library pattern with type classes
Type classes
Looking at the type class definition from wikipedia might quickly scare you away:
“In computer science, a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. Such a constraint typically involves a type class ‘T’ and a type variable ‘a’, and means that ‘a’ can only be instantiated to a type whose members support the overloaded operations associated with ‘T’.”
Basically what type classes allow is to add functionality to existing classes without needing to touch the existing classes. We could for instance add standard “comparable” functionality to Strings without having to modify the existing classes. Note that you could also just use implicit functions to add custom behavior (e.g the “Pimp my library pattern”, https://coderwall.com/p/k_1jzw/scala-s-pimp-my-library-pattern-example), but using type classes is much more safe and flexible. A good discussion on this can be found here (http://stackoverflow.com/questions/8524878/implicit-conversion-vs-type-c…).
So enough introducion, lets look at a very simple example of type classes. Creating a type class in scala takes a number of different steps.
The first step is to create a trait. This trait is the actual type class and defines the functionality that we want to provide. For this article we’ll create a very contrived example where we define a “Duplicate” trait. With this trait we duplicate a specific object. So when we get a string value of “hello”, we want to return “hellohello”, when we get an integer we return value*value, when we get a char ‘c’, we return “cc”. All this in a type safe manner.
Our typeclass is actually very simple:
trait Duplicate[A,B] { def duplicate(value: A): B }
Note that is look a lot like scala mix-in traits, but it is used completely different. Once we’ve got the typeclass definition, the next step is to create some default implementations. We do this in the trait’s companion object.
object Duplicate { // implemented as a singleton object implicit object DuplicateString extends Duplicate[String,String] { def duplicate(value: String) = value.concat(value) } // or directly, which I like better. implicit val duplicateInt = new Duplicate[Int, Int] { def duplicate(value: Int) = value * value } implicit val duplicateChar = new Duplicate[Char, String] { def duplicate(value: Char) = value.toString + value.toString } } }
As you can see we can do this in a couple of different ways. The most important part here is the implicit keyword. Using this keyword we can make these members implicity available under certain circumstances. When you look at the implementation you’ll notice that they are all very straigthforward. We just implement the trait we defined for specific types. In this case for a string, an integer and a character.
Now we can start using the type classes.
object DuplicateWriter { // import the conversions for use within this object import conversions.Duplicate // Generic method that takes a value, and looks for an implicit // conversion of type Duplicate. If no implicit Duplicate is available // an error will be thrown. Scala will first look in the local // scope before looking for implicits in the companion object // of the trait class. def write[A,B](value: A)(implicit dup: Duplicate[A, B]) : B = { dup.duplicate(value) } } // simple app that runs our conversions object Example extends App { import snippets.conversions.Duplicate implicit val anotherDuplicateInt = new Duplicate[Int, Int] { def duplicate(value: Int) = value + value } println(DuplicateWriter.write("Hello")) println(DuplicateWriter.write('c')) println(DuplicateWriter.write(0)) println(DuplicateWriter.write(0)(Duplicate.duplicateInt)) }
In this example we’ve create a DuplicateWriter which calls the duplicate function on the provide class by looking for a matching typecall implementation. In our Example object we also override the default duplicate function for the Int type with a custom one. In the last line we provide a specific Duplicate object to use by the DuplicateWriter. The output of this application is this:
20 100 HelloHello cc
If we run with an unsupported type (e.g a double):
println(DuplicateWriter.write(0d))
We get the following compile time messages (intellij IDE in this case).
Error:(56, 32) could not find implicit value for parameter dup: snippets.conversions.Duplicate[Double,B] println(DuplicateWriter.write(0d)) ^ Error:(56, 32) not enough arguments for method write: (implicit dup: snippets.conversions.Duplicate[Double,B])B. Unspecified value parameter dup. println(DuplicateWriter.write(0d)) ^
We can also customize the first of these messages by adding the following annotation to our trait/typeclass definition:
@implicitNotFound("No member of type class Duplicate in scope for ${T}") trait Duplicate[A,B] { def duplicate(value: A): B }
So that is a very quick introduction into type classes. As you can see, they provide a very easy way to add custom functionality to classes, even if you don’t control them. In the next snippet we’ll explore a couple of common, very useful type classes from the Scalaz library.
Reference: | Scala snippets 4: Pimp my library pattern with type classes. from our JCG partner Jos Dirksen at the Smart Java blog. |