- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Feature request : implicit resolution disambiguation
I'd like to make a case for an enhacement to implicit conversion resolution which
happened to be an unspecified behavior in 2.8.x.
I will describe a pattern that becomes possible with the 2.8.x behavior.
First some background on the problem that the pattern tries to solve :
SQL allows expressions like these :
aNullableInt * 5
which simply returns null if aNullableInt happens to be null.
Contrast this with the Scala equivalent, where aNullableInt is an Option[Int] :
aNullableInt * 5
it will not compile in Scala.
Squeryl's DSL enforce the typing rules of SQL which differs from Scala's.
This pages explains it : http://squeryl.org/type-mapping.html
Further examples :
class User(val id: Int, val age: Int, val birthDate: Date)
//The return types of these queries :
//q1-
val avgAge: Option[Int] = from(users)(u => compute(max(age))) // select max(u.age) from users
//q2-
val avgAgeTimesUserCount: Option[Double] = from(users)(u => compute(avg(age) times aLong))
//q3-
val oldest: Option[Date] = from(users)(u => compute(max(birthDate)))
in q1 for example, compute(max(age)) causes the return type to be Option[], since the table
can potentially be empty.
Here is how the SQL like type arythmetic is implemented :
primitive types in SQL expressions are converted to either NumericExpression or NonNumericExpression :
trait NumericExpression[Int]
trait NumericExpression[Option[Float]]
trait NonNumericExpression[Option[Date]]
SQL binary operations like Multiplication Addtion and Subtraction return an intermeriary type :
class BinaryAMSOp[Long,Float]
for which conversion back to NumericalExpression exist, and that is where the SQL like type arythmetic is
specified, here is a sample :
implicit def binaryOpConv23(op: BinaryAMSOp[LongType,LongType]) = new NumericalTypeConversion[LongType](op)
implicit def binaryOpConv24(op: BinaryAMSOp[LongType,FloatType]) = new NumericalTypeConversion[DoubleType](op)
implicit def binaryOpConv25(op: BinaryAMSOp[LongType,DoubleType]) = new NumericalTypeConversion[DoubleType](op)
implicit def binaryOpConv25bd(op: BinaryAMSOp[LongType,BigDecimalType]) = new NumericalTypeConversion[BigDecimalType](op)
implicit def binaryOpConv26(op: BinaryAMSOp[LongType,Option[ByteType]]) = new NumericalTypeConversion[Option[LongType]](op)
implicit def binaryOpConv27(op: BinaryAMSOp[LongType,Option[IntType]]) = new NumericalTypeConversion[Option[LongType]](op)
implicit def binaryOpConv28(op: BinaryAMSOp[LongType,Option[LongType]]) = new NumericalTypeConversion[Option[LongType]](op)
implicit def binaryOpConv29(op: BinaryAMSOp[LongType,Option[FloatType]]) = new NumericalTypeConversion[Option[DoubleType]](op)
The division operation returns BinaryDivOp[A,B] which goes through implicits like :
implicit def binaryOpConv92(op: BinaryDivOp[Option[DoubleType],IntType]) = new NumericalTypeConversion[Option[DoubleType]](op)
implicit def binaryOpConv102(op: BinaryDivOp[BigDecimalType,IntType]) = new NumericalTypeConversion[BigDecimalType](op)
implicit def binaryOpConv113(op: BinaryDivOp[Option[BigDecimalType],ByteType]) = new NumericalTypeConversion[Option[BigDecimalType]](op)
in order to become a NumericalExpression[A]
All Squeryl sql functions expect a TypedExpression[A] an ancester of NumericalExpression[A] and NonNumericalExpression[A],
for example :
def compute[A](e =>TypedExpression[A])
Now the problem :
Functions like the SQL 'max()' need to work on both NumericalExpression[A] and NonNumericalExpression[A] :
def max[A](e: NumericalExpression[A]) = new UnaryAgregateLengthNeutralOp[A](e, "max")
def max[A](e: NonNumericalExpression[A]) = new UnaryAgregateLengthNeutralOp[A](e, "max")
and UnaryAgregateLengthNeutralOp[A] can only become NumericalExpression[A] and NonNumericalExpression[A] via implicits
that will ensure that NumericalExpression[A] become NumericalExpression[Option[A]] (the same for NonNumericalExpression[A])
since that SQL aggregate functions turn everything into a nullable result
implicit def unaryOpConv1(op: UnaryAgregateLengthNeutralOp[ByteType]) = new NumericalTypeConversion[Option[ByteType]](op)
implicit def unaryOpConv2(op: UnaryAgregateLengthNeutralOp[IntType]) = new NumericalTypeConversion[Option[IntType]](op)
implicit def unaryOpConv5bd(op: UnaryAgregateLengthNeutralOp[BigDecimalType]) = new NumericalTypeConversion[Option[BigDecimalType]](op)
implicit def unaryOpConv7(op: UnaryAgregateLengthNeutralOp[Option[IntType]]) = new NumericalTypeConversion[Option[IntType]](op)
implicit def unaryOpConv11(op: UnaryAgregateLengthNeutralOp[DateType]) = new DateTypeConversion[Option[DateType]](op)
implicit def unaryOpConv15(op: UnaryAgregateLengthNeutralOp[BooleanType]) = new BooleanTypeConversion[Option[BooleanType]](op)
implicit def unaryOpConv16(op: UnaryAgregateLengthNeutralOp[Option[BooleanType]]) = new BooleanTypeConversion[Option[BooleanType]](op)
Since 2.9.0-RC1, these functions :
def max[A](e: NumericalExpression[A]) = new UnaryAgregateLengthNeutralOp[A](e, "max")
def max[A](e: NonNumericalExpression[A]) = new UnaryAgregateLengthNeutralOp[A](e, "max")
cannot coexist with the same name, the same happened with min() and nvl().
I haven't found a better workaround than removing the overload, i.e. renaming
max[A](e: NonNumericalExpression[A]) to maxNN[A](e: NonNumericalExpression[A]), one has to do :
from(users)(u => compute(maxNN(birthDate)))
//instead of :
from(users)(u => compute(max(birthDate)))
Perhaps the whole approach for implementing SQL's typing rules was wrong in the first place, I haven't found a better one,
but it did work rather well until now, and almost everything else (98%) still works.
Suggestions on a radically different approach are welcome, but I would hope that this other approach would have other
benefit that simply fixing this corner case.
So Informally the enhancement would be :
bFunc(
// the 2.9.0-RC1 behavior is to look for conversions for :
// AA[Z] --> N[Z]
// BB[Z] --> N[Z] // this conversion could be excluded in the search for implicits since the signature of aFunc(e: NN[Z]): BB[Z] is wrong at this point
aFunc(4F)
)
I will not try to formally specify the bahavior, I know it's not easy to specify this to have the least impact possible,
but intuitively it seems possible, since sugarring the expression forces the exclusion of aFunc(e: NN[Z]): BB[Z] :
bFunc({val z = aFunc(4F);z})
object ImplicitConvAmbiguity2 {
class N[T]
class NE[T] extends N[T]
class NN[T] extends N[T]
class AA[A]
class BB[A]
implicit def conv1(i: Float) = new NE[Float]
implicit def conv3(op: AA[java.util.TooManyListenersException]) = new N[java.util.TooManyListenersException]
implicit def conv4(op: AA[Float]) = new N[Float]
implicit def conv5(e: BB[java.util.GregorianCalendar]) = new N[java.util.GregorianCalendar]
def aFunc[A](a: NE[A]) = new AA[A]
def aFunc[A](a: NN[A]) = new BB[A]
def bFunc[T](e1: N[T]) = {}
bFunc(aFunc(4F))
}









