2011-08-06 14 views
6

Próbuję utworzyć typeclass Default, który dostarcza domyślną wartość dla danego typu. Oto co mam wymyślić do tej pory:Niejawna rozdzielczość parametru - ustawienie pierwszeństwa

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]) 
    } 
} 

Powyższa implementacja zachowuje się zgodnie z oczekiwaniami, z wyjątkiem przypadków BigInt i BigDecimal (i innych typów zdefiniowanych przez użytkownika, które są przypadki Numeric) gdzie daje null zamiast zero. Co powinienem zrobić, aby forNumeric miał pierwszeństwo przed forAnyRef i otrzymałem oczekiwane zachowanie?

Odpowiedz

11

forAnyRef niejawny jest wybrana, ponieważ jest dokładniej niż forNumeric według §6.26.3 „Przeładowanie Resolution” odniesienia Scala. Jest na to sposób, aby zmniejszyć jego priorytet przesuwając go cechę, że Default przebiega w następujący sposób:

trait LowerPriorityImplicits extends LowestPriorityImplicits { 
    this: Default.type => 

    implicit def forAnyRef[A >: Null] = withValue(null: A) 

} 

object Default extends LowerPriorityImplicits { 
    // as before, without forAnyRef 
} 

Ale to tylko część podstęp, bo teraz zarówno forAnyRef i forNumeric są tak specyficzne, jak każdy inny, i otrzymasz niejednoznaczny, niejawny błąd. Dlaczego? Cóż, forAnyRef otrzymuje dodatkowy punkt specyficzności, ponieważ ma nietrywialne ograniczenie na A: A >: Null. Co można zrobić, a następnie, aby dodać nietrywialne ograniczenie do forNumeric jest podwojenie go w Default:

implicit def forNumericVal[A <: AnyVal: Numeric] = withValue(implicitly[Numeric[A]].zero) 

implicit def forNumericRef[A <: AnyRef: Numeric] = withValue(implicitly[Numeric[A]].zero) 

Teraz, to dodatkowe ograniczenie sprawia forNumericVal i forNumericRef bardziej specyficzny, że forAnyRef dla typów gdzie Numeric jest dostępna.

+0

Dzięki za odpowiedź, Jean. Poprawiając twoje rozwiązanie, wymyśliłem inne rozwiązanie, które pozwala uniknąć tego małego powielania kodu. Zamieszczam to poniżej dla dobra czytelników. – missingfaktor

6

Oto kolejny sposób na rozwiązanie tego problemu, nie wymaga żadnego powielania kodu:

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]) 
    } 
} 
Powiązane problemy