I learnt about this technique from reading the source code of SXR. What follows is a step-by-step explanation.
Implicit conversion basics
Implicit conversions can make an object behave like it has another type. In particular, it can be used to add new methods to existing types. When a method is invoked on an object and no match is found, Scala will try to apply any implicit conversion currently in scope. Try typing this in the Scala REPL:implicit def intToHello(i: Int) = {
println("intToHello was applied")
new { def message = "hello from " + i }
}
In this contrived example, we define an implicit conversion to add a message method to integers. The conversion returns an anonymous instance that provides the method. I've added a print statement for illustrative purposes. We can now invoke message on any integer:scala> println(42.message)
intToHello was applied
hello from 42
A case of API incompatibility: Symbol.fullNameString
SXR is a Scala compiler plugin that generates an HTML cross-reference of the sources of a Scala project. In order to support partial recompilation and cross-project links, it needs to generate "stable" IDs for public compiler symbols; in particular, these IDs contain the full name of the symbol, which, in Scala 2.7.7, is retrieved with the method Symbol.fullNameString :def nameString(s: Symbol) = s.fullNameString(full source)
Unfortunately (for this particular situation!), symbols have been refactored in Scala 2.8. Class Symbol now extends scala.reflect.generic.Symbols.AbsSymbol, and the method we need is now called fullName. The code above no longer compiles:
<console>:12: error: value fullNameString is not a member of Demo.this.global.Symbol
def nameString(s: Symbol) = s.fullNameString
Implicits to the rescue
To compile the code with Scala 2.8, Symbol must behave like it has a fullNameString method. This is done exactly as in our first intToHello example:def nameString(s: Symbol) = s.fullNameString(full source)
private implicit def symCompat(sym: Symbol): SymCompat = new SymCompat(sym)
private final class SymCompat(s: Symbol) {
def fullNameString = s.fullName;
}
An implicit is used to add the missing fullNameString method, which delegates to the original fullName. The code now compiles with Scala 2.8. However, it no longer compiles with 2.7:
<console>:12: error: value fullName is not a member of Demo.this.global.SymbolThe 2.7 version of Symbol has no fullName method. Although the symCompat implicit will never be invoked at runtime in Scala 2.7 (since Symbol does defines fullNameString), this is still a compile error. To solve this, the missing method is added to the same implicit conversion:
def fullNameString = s.fullName;
def nameString(s: Symbol) = s.fullNameString(full source)
private implicit def symCompat(sym: Symbol): SymCompat = new SymCompat(sym)
private final class SymCompat(s: Symbol) {
def fullNameString = s.fullName; def fullName = sourceCompatibilityOnly
}
private def sourceCompatibilityOnly = assert(false, "should not get here")
To stress the fact that the method will never actually be invoked at runtime, it delegates to a method that throws an assertion error.

0 comments:
Post a Comment