According to Odersky, Scala is an acronym for “scalable language”, meaning it allows the language to change into whatever use you see fit. This is true to some extent; for example though Scala’s subtyping system is unsound, you can “expand” the language to make a better one. Of course it would be preferable for the default sub-typing system to work but oh well.

Another example of Scala “scaling” is the use of Type classes. The Typeclass system is not part of Scala, it is but a nifty pattern that uses Scala’s Implicit system to replicate what we call “Type classes” in Haskell and other languages. There’s plenty of literature on using typeclasses in Scala, so I won’t be showing how you use them. Instead I will be talking about where typeclass instances should go: meaning whether they should be “orphans” or not.

In Haskell, an orphan instance is an instance that is not defined in neither the file that the type is defined nor where the type class is defined. This means that it is “detached” from both definitions; meaning that in order to use it, you must import 3 things: the type, the type class (its functions) and the instance.

import Data.Foo

import Typeclass.Fooable

import Instances.FooInstance

In Haskell, this is pretty hard to do (though not impossible) and highly discouraged. It is hard to do because instances cannot be hidden from imports and they spread like wild-fire after being imported once (If file A defines an instance and file B imports A, and file C imports B, C will have the instances from file A). There are reasons why you want your instances to try to spread globally, more on that later.

Advertisement

Advertisement

In Scala, however, because type classes are just traits with a pattern and instances are just implicit values that can be imported or ignored, this is not the case. This means that the above CAN be done without worrying about the instance spreading.

This is where some people would think “well then Scala’s version is better since it gives you more control. You can import different instances for the same type”, but as it usually is in programming: more freedom just means more worries.

The great Edward Kmett gave a great talk explaining why type classes should spread globally and why being able to import instances willi-nilly is not as great as it sound.

The talk is long (though worth it) so I will summarize some points that are related to Scala.

Advertisement

Advertisement

Having more than one instance for a type breaks reasoning in your program. Pretty much when you see `fooDo(foo)` somewhere in your code, you have no idea what it is actually doing. You would need to find which instance is being imported, and given that most people do wildcard imports it won’t be easy. Additionally, due to Scala’s mysterious implicit resolution system, you aren’t guaranteed to be importing the one you think you are importing (or maybe you are? Who knows. Best case scenario, you get an implicit collision error. Worst case, you get no error...).

Gladly there is one place in Scala where you can place your typeclass instances without having to worry about any of that: the type’s companion object. Not only do you remove one import from your files (or many files if you decided to place all instances in the same package) since implicit resolution always checks the type’s companion object for implicits, but you also make your implicit scopes smaller and more manageable: you know where `foo`’s Fooable instance is, just go to its file.

SBT, recompilation and organization

Lastly, due to the way SBT recompilation works (where, if one file changes, it needs to recompile it and recompile every file that it touches), using non-orphan type class instances in a type’s companion object reduces recompile times.

Lets say `Foo` has a Play Json instance

final case class Foo(x: Int, y: String)

We make our Play Json instance and make it orphan in some other package `instance.play.json.FooInstance`. We want to keep instances together for organizational purposes, and so that we can just call `instances.play.json._` everywhere and get all json instances.

Advertisement

Advertisement

Time passes and it turns out that we need a new field in Foo, `z: Int`. So we change `Foo` and its json instance and compile. And then we see something this:

What? Changing two file led to 137 files recompiling? What’s even more shocking is that `Foo` and `fooDo` were only being used in one of those files. How can this be? Well SBT will recompile both the file where `Foo` is defined and where Foo’s instance is defined, but because we are importing `instances.play.json._` everywhere (lets be honest, importing implicits explicitly is annoying) that means that SBT now needs to recompile EVERY-SINGLE-FILE where that’s imported regardless of whether Foo and fooDo are used or not!

Advertisement

So we come to our sense and stop making orphan classes. Instead, we move play json’s instance for Foo into Foo’s companion object

final case class Foo(x: Int, y: String)

object Foo {

implicit val jsonInstance: Format[Foo] = ...

}

Now, like before, we need to add a new value to Foo and its instance. We do so and compile again, but this time we see this:

Nice! Because all of our changes were on Foo’s file, that and the file where Foo is used were the only 2 files that needed recompilation! Additionally, you now only need two imports to use Fooable and Foo.

Advertisement

Advertisement

Now this example was only for Play’s Json, but if you are using more than one type class in your system, things can get wild. Specially if you use a type class inside another (using Scalaz’s “Monoid” in Play Json, for example), a small change would cause huge a recompilation in your system, which kind defeats the purpose of SBT’s incremental compilation.

Multiple ways of “fooing” Foo

Ok, so this is nice and all but what if I do actually want to have multiple ways of calling `fooDo` on `Foo`? Well, more than likely having multiple instances isn’t what you want. You probably want a different way of representing `Foo`, which more often than not means a new type (`newtype` wrapper, in Scala called `Value Classes`). Value classes allow you to represent the “same” type differently at the compiler level, allowing you to represent it differently in the same type class without causing instance collisions.

Advertisement

For example, lets say we want a representation of `Foo` that only displays the `x` value when you covert it to Json. Instead of making a second instance for Play Json, we could just make a Value Class wrapper. Like so:

final case class JustX(run: Foo) extends AnyVal

object JustX {

implicit val jsonInstance: Format[JustX] = ...

}

For that instance, instead of forming the whole `Foo` object, we could just return a JsInteger of `x` or maybe something even more complicated. It might look annoying to type (sadly, there is no `deriving` mechanism in Scala) but it is well worth it.