Comment puis-je obtenir une instance de la classe de type associé à un contexte lié?
-
09-10-2019 - |
Question
Note:. Je pose cette question à lui répondre moi-même, mais d'autres réponses sont les bienvenus
Considérez la méthode simple suivante:
def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)
Je peux réécrire cela en utilisant un contexte lié ?? comme suit
def add[T: Numeric](x: T, y: T) = ??.plus(x,y)
mais comment puis-je obtenir une instance de type Numeric[T]
afin que je puisse appeler la méthode plus
?
La solution
En utilisant la méthode implicitement
La plus courante et approche générale est d'utiliser la méthode implicitement , défini dans Predef:
def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)
De toute évidence, cela est un peu bavard et on doit répéter le nom de la classe de type.
Référencez le paramètre PREUVES ( ne pas )
Une autre alternative est d'utiliser le nom du paramètre de preuve implicite généré automatiquement par le compilateur:
def add[T: Numeric](x: T, y: T) = evidence$1.plus(x,y)
Il est surprenant de constater que cette technique est encore légale, et il ne doit pas être invoqué dans la pratique puisque le nom du paramètre de preuve pourrait changer.
contexte d'un supérieur Kind ( introduire la méthode de context
)
Au lieu de cela, on peut utiliser une version plus musclée de la méthode implicitly
. Notez que la méthode implicite est définie comme
def implicitly[T](implicit e: T): T = e
Cette méthode repose simplement sur le compilateur d'insérer un objet implicite du type correct du champ environnant dans l'appel de méthode, puis retourne. Nous pouvons faire un peu mieux:
def context[C[_], T](implicit e: C[T]) = e
Cela nous permet de définir notre méthode de add
comme
def add[T: Numeric](x: T, y: T) = context.plus(x,y)
Les paramètres de type méthode context
Numeric
et T
sont déduites du champ d'application! Malheureusement, il y a des circonstances dans lesquelles cette méthode de context
ne fonctionnera pas. Quand un paramètre de type a plusieurs limites de contexte ou il y a des paramètres multiples avec bornes de contexte différent, par exemple. Nous pouvons résoudre ce dernier problème avec une version un peu plus complexe:
class Context[T] { def apply[C[_]]()(implicit e: C[T]) = e }
def context[T] = new Context[T]
Cette version nous oblige à préciser le type paramètre chaque fois, mais gère plusieurs paramètres de type.
def add[T: Numeric](x: T, y: T) = context[T]().plus(x,y)
Autres conseils
Au moins depuis Scala 2.9, vous pouvez faire ce qui suit:
import Numeric.Implicits._
def add[T: Numeric](x: T, y: T) = x + y
add(2.8, 0.1) // res1: Double = 2.9
add(1, 2) // res2: Int = 3
Cette réponse décrit une autre approche que les résultats dans plus lisible, code client auto-documenté.
Motivation
méthode context
que je l'ai décrit précédemment est une solution très générale qui fonctionne avec une classe de type, sans aucun effort supplémentaire. Cependant, il peut être indésirable pour deux raisons:
-
La méthode de
context
ne peut pas être utilisé lorsque le paramètre de type a de multiples bornes de contexte, étant donné que le compilateur n'a aucun moyen de déterminer quel contexte lié est destiné. -
La référence à la lisibilité des méfaits de la méthode de
context
générique du code client.
méthodes spécifiques de type classe
En utilisant une méthode qui est liée à la classe de type désiré rend le code client beaucoup plus lisible. Telle est l'approche utilisée dans la bibliothèque standard pour la classe de type Manifest:
// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m
// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure
Généraliser cette approche
Le principal inconvénient de l'utilisation de méthodes d'spécifiques de type de classe est que une méthode supplémentaire doit être définie pour chaque classe de types. Nous pouvons faciliter ce processus avec les définitions suivantes:
class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e }
object Implicitly { def apply[TC[_]] = new Implicitly[TC] }
Ensuite, une nouvelle méthode implicitement style spécifique de classe de type peut être défini, pour une classe de type:
def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]
Enfin, le code client peut utiliser le suit comme Implicitement:
def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)