Orphan instances in Scala
Orphan Instances in Scala
Type classes in Scala are interesting, in that even though they aren’t part of the language, they are widely used. This is mostly attributed to type classes’ usefulness and the ability for Scala.
Type classes first appeared in Haskell as a way to implement overloading in a principled way. Since then many other applications have been found for them. I will not describe type classes in detail (if you are unfamiliar with them, you should read Sohum Banerjea’s excellent intro), instead I would like to focus on orphan instances.
An orphan instance in Scala means an instance that exists neither in the type’s companion object nor the type class’ companion object. For example:
In the above example,
FooInst is said to be an “orphan instance”. This is
because it is not attached to neither
Foo (via their companion
objects). This means that in order to use the type class, we must import three
packages: the package with
Foo, the package with
DoFoo and the package with
the instance declaration (in the code above, we used objects to represent
Something you will notice from this style is that we can now define a different
instance for Foo and import it in order to change
Now we can change the way the program works by simply changing an import! If the previous statement didn’t scare you, it should have.
The examples that we have used so far have been quite simple, we are importing a couple of packages and that’s it. But in reality, your import statement is probably 10-20 (or maybe even more) lines long, and filled with wild-card imports, this leads to a couple of problems.
The first problem has to do with referential transparency. If you see
1 + 1
anywhere in your code, you would like it to always return
2; it would be
quite odd if in some parts of your code
1 + 1 = 3. Same applies to
type-classes, if we see
doSomething(Foo("Hello")), it would be great if that
didn’t change next time we see it. However, having multiple orphan instances
breaks this transparency; what
doSomething(Foo("Hello")) does is now
dependent on one of the many imports at the top of the file. This can make
refactoring and cleaning a very scary process.
The second problem you might encounter when using orphans is that changing a
single line in your (very long) import statement can have drastic effects on
the program while still compiling. This happens when declaring overlapping
instances. This won’t happen with the example above since importing both
InstanceFoo2 (and then trying to call
have given an implicit conflict compile error due to both of them being instances
for the same exact type (Foo). However, this doesn’t happen with overlapping
Above, we made a generic instance for the container “List”. As part of the
instance definition, we stated that the inner type has to also define a
instance; this means that
doSomething(List(Foo("Hello"), Foo("World"))) will
work fine. However, we also defined a specific instance for
would happen if we import both of them? According to the Scala language
If there are several eligible arguments which match the implicit parameter’s type, a most specific one will be chosen using the rules of static overloading resolution (§6.26.3).
This means that the
ListofFooInstance will be picked if we import both. But,
more importantly, if we remove it from the import statement, it will then pick
ListInstance and continue to compile even though it is doing something
very different. This can, again, make cleaning and refactoring a painful
So what if you do actually want to have a different representation (in a type-class) for the same type? This is where value classes come to the rescue. A value class allows us to create a new type that exists only at compile time: at runtime, it is replaced by the underlying type. But because it is a different type, we can create a new, non-orphan instance for it. And because it is a simple type wrapper, we can quickly and easily switch from it to the underlying type:
Of course, this means that in order to use this new functionality you have to
Foo, like so
doSomething(NewFoo(Foo("Hello"))). However, it is now
explicit that this is different; we can expect that line to do the same thing
everywhere and never what
doSomething(Foo("Hello")) would do.
Now, this does not mean you should avoid orphans like the plague. There are
certain cases where they can’t be avoided (or avoiding them would be too annoying).
For example, if you have two libraries: one has a case class you would like to
use and the other has a type class you would also like to use. You could create
a new type wrapper (like
NewFoo) around the class, but if you are going to
be using said class extensively, then maybe it is ok to simply create an orphan
instance for it (just make sure you don’t create more than one). If you have
access to either the type class or the case class, just place the instance in
either one’s companion object.
In conclusion, orphan instances can be helpful at time, but they can quickly grow unwieldy as your codebase grows and becomes more complex if not kept in check. Value classes can help avoid them all together, at the cost of having to wrap unwrap values. But these may be worth the cost as your codebase grows and your type classes become more complex.