Неявное разрешение параметров - установка приоритета

Я пытаюсь создать 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])
  }
}