Scala is a pretty good functional programming language, but its type class syntax is pretty ugly to use. Thankfully, there is a pattern you can use to simplify the use of your type classes, making them almost as easy to use as regular inheritance based OOP.
Type classes are a very handy concept first pioneered in Haskell, which allows polymorphism like inheritance, but without requiring the foresight of inheritance. By that I mean that type class relationships between types can be defined for any type after that type has been defined, rather than during that type’s definition.
Below, I have the definition of a type class called depth.
trait Depth[P[_]] { def depth(self: P[_]): Int }
This type class can be implemented for any type constructor like so:
object Depth { implicit object ListDepth extends Depth[List] { def depth(self: List[_]) = x.size } }
The code above basically tells Scala how Depth is implemented for the type constructor List. This is all well and good, but when we go to use our type class…
object Main extends App { List(1,2,3).depth //won't compile, list does not have depth defined def fn[T[_]: Depth](t: T[_]) = { t.depth //Also doesn't work implicitly[Depth[T]].depth(t) //yuck } }
things get very messy very quickly. Thankfully, with just a few tweaks, our typeclass can be as easy to use as regular polymorphism!
trait Depth[P[_]] { def depth(self: P[_]): Int class Ops[T](self: P[T]) { def depth = Depth.this.depth(self) } } object Depth { implicit object ListDepth extends Depth[List] { def depth(self: List[_]) = x.size } //This implicit converts anything that has an implementation of Depth into the Depth.Ops class implicit def implicits[T, P[T]](x: P[T])(implicit ev: Depth[P]) = new ev.Ops(x) }
As you can see, we added a new def to our Depth companion object called implicits and a class named Ops to our Depth trait. We simplify the depth function by making it apply itself to the depth object it belongs to. The implicit def called implicits converts any object X that has an implementation of the Depth type class into a Depth#Ops object. This makes our syntax in main easy to use and much nicer looking:
import Depth import Depth._ object Main extends App { List(1,2,3).depth //now this works just like if List inherited from Depth def fn[T[_]: Depth](t: T[_]) = { t.depth //So does this! } }
Type classes make certain code patterns much easier to deal with. For example, earlier today I had two different datastructures with similar methods I wanted to benchmark against each other. Problem is, they shared no inheritance, so under normal polymorphism I would have to either implement some kind of shared parentage or implement multiple near copies of the same benchmarking code. With typeclasses, there was a much easier third option. Implement a typeclass with the methods signatures I was going to use, and make both types implement that typeclass. Then I just needed to pass in a datastructure to the benchmark function and it would do the rest. Code gets reused and everyone is happy.
What are the downsides to the pattern above and type classes in general? They are:
- Type classes are terrible for documentation, since any and all implementations of the type class can be defined anywhere in your code.
- Method signature collisions between type classes implemented by a type can cause weird “method not defined” compiler errors.
- Generic syntax along with implicits definitions can be finicky, and sometimes it takes work to figure out why your implicit is not triggering.
- The implicit def that enhances the typeclass implementations must have a unique name. Thanks to type erasure, Scala can’t differentiate these methods solely on type signature. Collisions in the names of these methods will generate “method not defined” compiler errors.
All in all, typeclasses are a very nice and powerful tool for polymorphism in Scala!
edit: As Nullable has brought up, this can be done with implicit classes and view bounds in a more succint way:
trait Depth[T] extends Any { def depth: Int } object Depth { implicit class ListDepth[T](val self: List[T]) extends AnyVal with Depth[List[T]] { def depth = self.size } } object Main extends App { List(1,2,3).depth // 3 def fn[T <% Depth[T]](t: T) = t.depth //compiles }
However, Depth is no longer useable as a type class, meaning implicitly goes out the window.
—-
This post was inspired by Scala – The Flying Sandwich Parts by eed3si9n
Why not just make ListDepth an implicit AnyVal class instead of an implicit singleton?
Example of the above (beacuse I can’t find the editing): https://gist.github.com/dontcare4free/550a301d5fe08841418c
Yes, I just got done testing this a moment ago, and unfortunately it’s not treated as a type class anymore. def fn[T: Depth](t: T) = t.depth doesn’t work for it.
Here’s a gist: https://gist.github.com/markehammons/81edbf78b3872b45b61e
It does, however, work if you use view bounds instead of view bounds (“def fn[T <% Depth[T]](t: T)" instead of "def fn[T : Depth](t: T)"). A little more typing for the definition, but it can then be used much more pleasantly (no extra boilerplate in the definition, and no need to explicitly call implicitly all the time), IMO.
Gist: https://gist.github.com/dontcare4free/95db73fa805f72ece992
I hadn’t had much of a look at view bounds before, and they seem to be pretty old, but looking at it implicit conversions and view bounds seem to be able to cover all the ground that context bounds + type classes cover and more.
Anyway, yes, using implicit classes + view bounds seems to be a cleaner way of doing this at the moment.
Ok, so I was going to try to bench both ways, see if there was any difference runtime-wise, and I’ve hit a snag:
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
gistfile1.scala
hosted with ❤ by GitHub
Any ideas on how to deal with this?
I’m not sure how that would look. A value class has to have one val member in its constructor. The implicit value class would automatically inject the typeclass methods into all Lists, but as they are written they wouldn’t make much sense. Why would I want to call List(1,2,3).depth(List(1,2,3))?
I believe your post confuses a lot of people (see discussion on reddit), because you did not show the obvious implementation: Leave Depth as you defined it, but write Main as follows:
object Main extends App {
import Depth._
List(1,2,3).depth
def fn[T[_]](t: T[_])(implicit d: Depth[T]) = {
d.depth(t)
}
}
I guess you wanted to avoid the import and jumped through great hoops to do this, but writing the import explicitly is the correct style, IMO.
I was more wanting to avoid using implicitly or have a manually defined implicit field. I really dislike them because they are decently verbose and they make Fn’s signature long compared to what I’d do for two classes with a common parent.
Is this fine?
Also, while i have you on the phone, one of the commenters on my blog suggested using implicit value classes and view bounds instead of typeclasses with context bounds. On the surface, his approach is simpler, but I’m wary because until now I’ve never seen a view bound used.
Is there a reason they’ve fallen out of favor compared to context bounds?
I was suggested this blog by my cousin. I’m not sure whether this post is written by him as no one else know such detailed about my difficulty. You are wonderful! Thanks! ceadfdfbddef