Implicits on Implicits
Friday, July 17th, 2009I have recently made a change to ScalaQuery which enables the use of any type for a column without needing specific Column classes and factory methods on Table for it. For this I used an approach which I had not considered earlier and which I wasn’t even sure would work.
Scala (at least in version 2.7) only considers single function calls for implicit conversions. Take the following definitions for example:
class A class B(a: A) class C(b: B) implicit def aToB(a: A) = new B(a) implicit def bToC(b: B) = new C(b) val a = new A def useB(b: B) = ... def useC(c: C) = ...
You can call useB(a) because the compiler finds the implicit conversion aToB(a) but you cannot call useC(a) — the compiler does not try the chained call bToC(aToB(c)).
But this does not mean that an implicit conversion may not rely on an implicit value! The following works just fine:
trait Column[T] class ConstColumn[T](value: T, tm: TypeMapper[T]) extends Column[T] implicit def valueToColumn[T](value: T)(implicit tm: TypeMapper[T]) = new ConstColumn(value, tm) trait TypeMapper[T] implicit object IntTypeMapper extends TypeMapper[Int] val c1: Column[_] = 42
The Scala compiler recognizes that valueToColumn[Int] provides the desired conversion from Int to Column[_] and then looks for the required implicit TypeMapper[Int] value, which is available through the implicit IntTypeMapper object.
In ScalaQuery, the real TypeMapper contains only a few methods and there are predefined TypeMappers for most of the basic types used by JDBC. That’s nice because it removes some redundancy from my code base but the better news is that it allows you to write your own TypeMappers. For example, if you wanted to get the java.lang.Integer columns back which I removed in favor of Int and Option[Int], you could add this implicit object to your code:
implicit object IntegerTypeMapper extends TypeMapper[java.lang.Integer] {
def zero = null
def sqlType = java.sql.Types.INTEGER
def setValue(v: java.lang.Integer, p: PositionedParameters) =
if(v eq null) p.setIntOption(None) else p.setIntOption(Some(v.intValue))
def setOption(v: Option[java.lang.Integer], p: PositionedParameters) = v match {
case Some(null) => p.setIntOption(None)
case Some(i) => p.setIntOption(Some(i.intValue))
case None => p.setIntOption(None)
}
def nextValue(r: PositionedResult) = r.nextIntOption match {
case None => null
case Some(i) => java.lang.Integer.valueOf(i)
}
}
The same should work for any database engine- or domain-specific types.