2011-08-06 7 views
6

Estoy intentando crear una clase de tipos Default que suministre el valor predeterminado para un tipo determinado. Esto es lo que he encontrado hasta el momento:Resolución de parámetros implícitos: configuración de la precedencia

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

La implementación anterior se comporta como se esperaba, a excepción de los casos de BigInt y BigDecimal (y otros tipos definidos por el usuario que son instancias de Numeric) donde da null vez de cero. ¿Qué debo hacer para que forNumeric tenga prioridad sobre forAnyRef y obtengo el comportamiento que espero?

Respuesta

11

El forAnyRef implícita es elegido porque es más específica de acuerdo con forNumeric §6.26.3 “Resolución sobrecarga” de la referencia Scala. Hay una manera de reducir su prioridad moviéndolo a un rasgo que Default se extiende, como esto:

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

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

} 

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

Pero eso es sólo parte del truco, porque ahora tanto forAnyRef y forNumeric son tan específicos como los demás, y obtendrá un error implícito ambiguo. ¿Porqué es eso? Bueno, forAnyRef obtiene un punto de especificidad adicional porque tiene una restricción no trivial en A: A >: Null. Lo que puede hacer a continuación, añadir una restricción no trivial a forNumeric, es duplicar en Default:

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

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

Ahora, esta restricción adicional hace forNumericValforNumericRef y más específica que forAnyRef para este tipo donde un Numeric está disponible.

+0

Gracias por la respuesta, Jean. Al ajustar tu solución, se me ocurrió otra solución que evita esta pequeña duplicación de código. Lo publico a continuación para el beneficio de los lectores. – missingfaktor

6

Aquí hay otra manera de resolver el problema, no requiere ninguna duplicación de código:

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