Structural typing and type classes in Scala
Let's start with a concrete example: You may know the Closeable interface in Java. It allows you to handle resources that may be closed in a concise way. The problem with such a solution is that it will only work with types already implementing it. And the list is quite long, but it won't include that new shiny class you created yesterday the class of that shiny open source library you use; and, sadly enough, you can't modify that class.
We would like to be able to use duck typing: If it has a close method, its closeable. In Scala you can achieve that with structural typing:
def withResource[T](r: {def close(): Unit}, f: => T): T = {
try {
f
} finally {
// let me simplify things here by assuming close is not throwing exceptions itself
r.close()
}
}
Here the type of r may be any type that has a close method that takes no parameters and returns unit. The interesting thing about this is that you can use your withResource function with types you didn't design it for, as far as they have the structure you defined. Hence the name.
The problem with this solution is that it has runtime implications, since Scala will use introspection at runtime.
But we can do better. Enter type classes:
trait Closeable[R] {
def close(r: R): Unit
}
object Closeable {
implicit object SqlConnectionIsCloseable extends Closeable[Connection] {
def close(conn: Connection) = conn.close()
}
}
def withResource[R:Closeable, T](r: R, f: => T): T = {
try {
f
} finally {
implicitly[Closeable[R]].close(r)
}
}
Closeable is a type class: It defines a class of types that includes all the types that can be closed, just like the structural type we used before. But, then, you add a type to the class by defining an implicit object that tells the compiler how that type belongs to the type class and how the close method is implemented for that particular class.
When you define a method that takes a parameter that "is closeable" (r in the example), you simply use a context bound telling the compiler that there should be an implicit object whose type is Closeable[R] where R is the type of the parameter. Then, implicitly[Closeable[R]] is a trick to get that instance.
Now you can extend the solution by just creating new implicit objects for new types you find that can be closed somehow, even if the method is not named close.
Written by Jordi Pradel
Related protips
3 Responses
A great solution to avoid horrible structural classes.
@sksamuel Thanks! I don't hate structural classes as much as you seem to do, but I still prefer the type classes approach. :)
Edited: As @ignasi35 correctly pointed out (via Twitter), in the first paragraph, the problem of a class having to inherit an interface is not that much of a problem if it is your "shiny new class". The problem arises when you can't modify the class.