Неявное разрешение параметров - установка приоритета
Я пытаюсь создать typeclass Default
, который поставляет значение по умолчанию для данного типа. Вот что я придумал до сих пор:
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])
}
}
Вышеприведенная реализация ведет себя так, как ожидалось, за исключением случаев BigInt
и BigDecimal
(и других определяемых пользователем типов, которые являются экземплярами Numeric
), где он дает null
вместо нуля. Что мне делать, чтобы forNumeric
имел приоритет над forAnyRef
, и я получаю ожидаемое поведение?
Ответы
Ответ 1
Неявный forAnyRef
выбирается, потому что он более специфичен, чем forNumeric
в соответствии с §6.26.3 "Разрешение перегрузки" справочника Scala. Существует способ уменьшить свой приоритет, переместив его к признаку, который Default
расширяется, например:
trait LowerPriorityImplicits extends LowestPriorityImplicits {
this: Default.type =>
implicit def forAnyRef[A >: Null] = withValue(null: A)
}
object Default extends LowerPriorityImplicits {
// as before, without forAnyRef
}
Но это только часть трюка, потому что теперь как forAnyRef
, так и forNumeric
являются такими же конкретными, как и у других, и вы получите неоднозначную неявную ошибку. Почему это? Ну, forAnyRef
получает дополнительную особенность, потому что у нее есть нетривиальное ограничение на A
: A >: Null
. Тогда вы можете добавить нетривиальное ограничение к forNumeric
, чтобы удвоить его в Default
:
implicit def forNumericVal[A <: AnyVal: Numeric] = withValue(implicitly[Numeric[A]].zero)
implicit def forNumericRef[A <: AnyRef: Numeric] = withValue(implicitly[Numeric[A]].zero)
Теперь это дополнительное ограничение делает forNumericVal
и forNumericRef
более конкретным, чем forAnyRef
для типов, где доступен Numeric
.
Ответ 2
Вот еще один способ решить проблему, не требует дублирования кода:
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])
}
}