问题
I am trying to create a typeclass Default
that supplies the default value for a given type. Here is what I have come up with so far:
trait Default[A] {
def value: A
}
object Default {
def withValue[A](a: A) = new Default[A] {
def value = a
}
def default[A : Default]: A = implicitly[Default[A]].value
implicit val forBoolean = withValue(false)
implicit def forNumeric[A : Numeric] =
withValue(implicitly[Numeric[A]].zero)
implicit val forChar = withValue(' ')
implicit val forString = withValue("")
implicit def forOption[A] = withValue(None : Option[A])
implicit def forAnyRef[A >: Null] = withValue(null : A)
}
case class Person(name: String, age: Int)
case class Point(x: Double, y: Double)
object Point {
implicit val pointDefault = Default withValue Point(0.0, 0.0)
}
object Main {
def main(args: Array[String]): Unit = {
import Default.default
println(default[Int])
println(default[BigDecimal])
println(default[Option[String]])
println(default[String])
println(default[Person])
println(default[Point])
}
}
The above implementation behaves as expected, except for the cases of BigInt
and BigDecimal
(and other user defined types that are instances of Numeric
) where it gives null
instead of zero. What should I do so that forNumeric
takes precedence over forAnyRef
and I get the behavior I expect?
回答1:
The forAnyRef
implicit is chosen because it is more specific than forNumeric
according to §6.26.3 “Overloading Resolution” of the Scala reference. There is a way to reduce its priority by moving it to a trait that Default
extends, like this:
trait LowerPriorityImplicits extends LowestPriorityImplicits {
this: Default.type =>
implicit def forAnyRef[A >: Null] = withValue(null: A)
}
object Default extends LowerPriorityImplicits {
// as before, without forAnyRef
}
But that's only part of the trick, because now both forAnyRef
and forNumeric
are as specific as each other, and you'll get an ambiguous-implicit error. Why is that? Well, forAnyRef
gets an extra specificity point because it has a non-trivial constraint on A
: A >: Null
. What you can do then, to add a nontrivial constraint to forNumeric
, is to double it in Default
:
implicit def forNumericVal[A <: AnyVal: Numeric] = withValue(implicitly[Numeric[A]].zero)
implicit def forNumericRef[A <: AnyRef: Numeric] = withValue(implicitly[Numeric[A]].zero)
Now, this additional constraint makes forNumericVal
and forNumericRef
more specific that forAnyRef
for types where a Numeric
is available.
回答2:
Here is another way to solve the problem, doesn't require any code duplication:
trait Default[A] {
def value: A
}
object Default extends LowPriorityImplicits {
def withValue[A](a: A) = new Default[A] {
def value = a
}
def default[A : Default]: A = implicitly[Default[A]].value
implicit val forBoolean = withValue(false)
implicit def forNumeric[A : Numeric] =
withValue(implicitly[Numeric[A]].zero)
implicit val forChar = withValue(' ')
implicit val forString = withValue("")
implicit def forOption[A] = withValue(None : Option[A])
}
trait LowPriorityImplicits { this: Default.type =>
implicit def forAnyRef[A](implicit ev: Null <:< A) = withValue(null : A)
}
case class Person(name: String, age: Int)
case class Point(x: Double, y: Double)
object Point {
implicit val pointDefault = Default withValue Point(0.0, 0.0)
}
object Main {
import Default.default
def main(args: Array[String]): Unit = {
println(default[Int])
println(default[BigDecimal])
println(default[Option[String]])
println(default[String])
println(default[Person])
println(default[Point])
}
}
来源:https://stackoverflow.com/questions/6966825/implicit-parameter-resolution-setting-the-precedence