implicit parameter precedence again

Submitted by eed3si9n on Sat, 01/07/2012 - 06:45

Scala the language is one of the most elegant, expressive, consistent, and pragmatic languages. From pattern matching to the uniform access principle, it got so many things right. And Scala the ecosystem and Scala the community only makes it better.

In Scala 2.9.1, locally declared implicits are preferred over imported ones. The problem is that the spec does not cover such behavior. My original hypothesis was that either I did not understand the spec correctly, or the spec was wrong. Based on the assumptions, I set out to explore the implicits resolution precedence last week. Like MythBusters say, the best kind of result is when you get something totally unexpected. It turns out that both of the hypotheses were wrong.

My understanding of the relevant part of the spec was correct, and spec was correct as well. According to SI-5354, what's wrong was the compiler implementation:

The reason why the second example [with locally declared implicits] slipped through is considerably more devious: When checking the Foo.x implicit, a CyclicReference error occurs which causes the alternative to be discarded.

In other words, the fact that locally declared implicits were being prioritized was due to a bug. This has been corrected in the master branch and can be tested using a 2.10 nightly.

2) implicit scope, which contains all sort of companion objects and package object that bear some relation to the implicit's type which we search for (i.e. package object of the type, companion object of the type itself, of its type constructor if any, of its parameters if any, and also of its supertype and supertraits).

If at either stage we find more than one implicit, static overloading rule is used to resolve it.

static overloading rules

The relative weight of an alternative A over an alternative B is a number from 0 to 2, defined as the sum of
- 1 if A is as specific as B, 0 otherwise, and
- 1 if A is defined in a class or object which is derived from the class or object defining B, 0 otherwise.

A class or object C is derived from a class or object D if one of the following holds:
- C is a subclass of D, or
- C is a companion object of a class derived from D, or
- D is a companion object of a class from which C is derived.

An alternative A is more specific than an alternative B if the relative weight of A over B is greater than the relative weight of B over A.

For views, if A is as specific view as B, A gets a relative weight of 1 over B.

If A is defined in a derived class in which B is defined, A gets another relative weight.

without the import tax

Now that we have cleared out the precedence, let's review where we can define our implicits to design an API without the import tax.

Category 1 (implicits loaded to current scope) should be avoided if you want to let your user write their code in arbitrary packages and classes and want to avoid import.

On the other hand, the entire Category 2 (implicit scope) is wide open.

companion object of type T (or its part)

The first place to consider is the companion object of an associated type (in this case a type constructor):

Unfortunately, however, "foo".yell won't work outside of yeller package because the compiler doesn't know about possible the implicit conversion. One workaround is to break into Category 1 (implicits loaded to current scope) by calling import yeller._:

object Main extends App {import yeller._
println("banana".yell)}

This is not bad since the import is consolidated into one thing.

user's package object

Can we get rid of the import statement? Another place in Category 1 is the user's package object, to which they can mixin Implicit trait:

summary

Contrary to the conclusion I arrived by observing 2.9.1, there is no such thing as "current scope clause" while resolving multiple implicits. There are only Category 1, Category 2, and static overloading resolution.