Types d'implications
Implicits dans Scala fait référence à une valeur qui peut être transmise "automatiquement", pour ainsi dire, ou à une conversion d'un type à un autre qui est effectuée automatiquement.
Conversion implicite
En parlant brièvement de ce dernier type, si l' on appelle une méthode m
sur un objet o
d'une classe C
, et que classe ne prend pas en charge la méthode m
, alors Scala cherchera une conversion implicite de C
quelque chose qui fait support m
. Un exemple simple serait la méthode map
sur String
:
"abc".map(_.toInt)
String
ne prend pas en charge la méthode map
, mais le StringOps
fait, et il y a une conversion implicite de String
to StringOps
available (voir implicit def augmentString
sur Predef
).
Paramètres implicites
L'autre type d'implicite est le paramètre implicite . Celles-ci sont passées aux appels de méthode comme tout autre paramètre, mais le compilateur essaie de les remplir automatiquement. Sinon, il se plaindra. On peut passer ces paramètres explicitement, c'est ainsi que l'on utilise breakOut
, par exemple (voir question à propos breakOut
, un jour où vous vous sentez prêt à relever un défi).
Dans ce cas, il faut déclarer la nécessité d'un implicite, comme la foo
déclaration de méthode:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Afficher les limites
Il y a une situation où un implicite est à la fois une conversion implicite et un paramètre implicite. Par exemple:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
La méthode getIndex
peut recevoir n'importe quel objet, tant qu'une conversion implicite est disponible de sa classe vers Seq[T]
. Pour cette raison, je peux passer un String
à getIndex
, et cela fonctionnera.
Dans les coulisses, le compilateur se transforme seq.IndexOf(value)
en conv(seq).indexOf(value)
.
C'est tellement utile qu'il y a du sucre syntaxique pour les écrire. L'utilisation de ce sucre syntaxique getIndex
peut être définie comme ceci:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Ce sucre syntaxique est décrit comme une vue liée , semblable à une borne supérieure ( CC <: Seq[Int]
) ou une borne inférieure ( T >: Null
).
Limites du contexte
Un autre modèle courant dans les paramètres implicites est le modèle de classe de type . Ce modèle permet la fourniture d'interfaces communes aux classes qui ne les ont pas déclarées. Il peut à la fois servir de modèle de pont - séparant les préoccupations - et de modèle d'adaptateur.
La Integral
classe que vous avez mentionnée est un exemple classique de modèle de classe de type. Un autre exemple sur la bibliothèque standard de Scala est Ordering
. Il existe une bibliothèque qui fait un usage intensif de ce modèle, appelée Scalaz.
Voici un exemple de son utilisation:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Il y a aussi du sucre syntaxique pour cela, appelé un contexte lié , qui est rendu moins utile par la nécessité de se référer à l'implicite. Une conversion directe de cette méthode ressemble à ceci:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Les limites de contexte sont plus utiles lorsque vous avez juste besoin de les passer à d'autres méthodes qui les utilisent. Par exemple, la méthode sorted
on a Seq
besoin d'un implicite Ordering
. Pour créer une méthode reverseSort
, on pourrait écrire:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Parce qu'il a Ordering[T]
été implicitement transmis à reverseSort
, il peut ensuite le transmettre implicitement à sorted
.
D'où viennent les Implicits?
Lorsque le compilateur voit le besoin d'un implicite, soit parce que vous appelez une méthode qui n'existe pas sur la classe de l'objet, soit parce que vous appelez une méthode qui nécessite un paramètre implicite, il recherchera un implicite qui répondra au besoin .
Cette recherche obéit à certaines règles qui définissent les implications visibles et celles qui ne le sont pas. Le tableau suivant montrant où le compilateur recherchera les implicits est tiré d'une excellente présentation sur les implicits par Josh Suereth, que je recommande vivement à tous ceux qui souhaitent améliorer leurs connaissances Scala. Il a depuis été complété par des commentaires et des mises à jour.
Les implicits disponibles sous le numéro 1 ci-dessous ont priorité sur ceux sous le numéro 2. En dehors de cela, s'il existe plusieurs arguments éligibles qui correspondent au type du paramètre implicite, un argument plus spécifique sera choisi en utilisant les règles de résolution de surcharge statique (voir Scala Spécification §6.26.3). Des informations plus détaillées se trouvent dans une question à laquelle je renvoie à la fin de cette réponse.
- Premier aperçu dans la portée actuelle
- Implicits définis dans le périmètre actuel
- Importations explicites
- importations de caractères génériques
Même portée dans d'autres fichiers
- Regardez maintenant les types associés dans
- Objets compagnons d'un type
- Portée implicite du type d'un argument (2.9.1)
- Portée implicite des arguments de type (2.8.0)
- Objets extérieurs pour les types imbriqués
- Autres dimensions
Donnons-leur quelques exemples:
Implications définies dans la portée actuelle
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Importations explicites
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Importations génériques
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Même portée dans d'autres fichiers
Edit : Il semble que cela n'a pas une priorité différente. Si vous avez un exemple qui démontre une distinction de priorité, veuillez faire un commentaire. Sinon, ne comptez pas sur celui-ci.
C'est comme le premier exemple, mais en supposant que la définition implicite se trouve dans un fichier différent de son utilisation. Voir aussi comment les objets de package peuvent être utilisés pour introduire des implicits.
Objets compagnons d'un type
Il existe deux compagnons d'objet à noter ici. Tout d'abord, l'objet compagnon de type "source" est examiné. Par exemple, à l'intérieur de l'objet, Option
il y a une conversion implicite en Iterable
, donc on peut appeler des Iterable
méthodes Option
ou passer Option
à quelque chose qui attend un Iterable
. Par exemple:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
Cette expression est traduite par le compilateur en
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Cependant, List.flatMap
attend un TraversableOnce
, qui Option
ne l'est pas. Le compilateur regarde ensuite à Option
l' intérieur du compagnon d'objet et trouve la conversion en Iterable
, qui est un TraversableOnce
, ce qui rend cette expression correcte.
Deuxièmement, l'objet compagnon du type attendu:
List(1, 2, 3).sorted
La méthode sorted
prend un implicite Ordering
. Dans ce cas, il regarde à l'intérieur de l'objet Ordering
, compagnon de la classe Ordering
, et y trouve un implicite Ordering[Int]
.
Notez que les objets compagnons des super classes sont également examinés. Par exemple:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
C'est ainsi que Scala a trouvé l'implicite Numeric[Int]
et Numeric[Long]
dans votre question, d'ailleurs, comme ils se trouvent à l'intérieur Numeric
, non Integral
.
Portée implicite du type d'un argument
Si vous avez une méthode avec un type d'argument A
, la portée implicite de type A
sera également prise en compte. Par "portée implicite", j'entends que toutes ces règles seront appliquées de manière récursive - par exemple, l'objet compagnon de A
sera recherché pour les implicites, conformément à la règle ci-dessus.
Notez que cela ne signifie pas que la portée implicite de A
sera recherchée pour les conversions de ce paramètre, mais de l'expression entière. Par exemple:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Ceci est disponible depuis Scala 2.9.1.
Portée implicite des arguments de type
Cela est nécessaire pour que le modèle de classe de type fonctionne vraiment. Considérez Ordering
, par exemple: il est livré avec des implicites dans son objet compagnon, mais vous ne pouvez pas y ajouter des éléments. Alors, comment pouvez-vous faire un Ordering
pour votre propre classe qui est automatiquement trouvé?
Commençons par l'implémentation:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
Alors, réfléchissez à ce qui se passe lorsque vous appelez
List(new A(5), new A(2)).sorted
Comme nous l'avons vu, la méthode sorted
attend un Ordering[A]
(en fait, elle attend un Ordering[B]
, où B >: A
). Il n'y a rien de tel à l'intérieur Ordering
, et il n'y a pas de type "source" sur lequel regarder. Évidemment, il le trouve à l'intérieur A
, qui est un argument de type de Ordering
.
C'est aussi ainsi que les différentes méthodes de collecte qui attendent de CanBuildFrom
fonctionner: les implicites se trouvent à l'intérieur des objets compagnons aux paramètres de type de CanBuildFrom
.
Remarque : Ordering
est défini comme trait Ordering[T]
, où T
est un paramètre de type. Auparavant, je disais que Scala regardait à l'intérieur des paramètres de type, ce qui n'a pas beaucoup de sens. L'implicite recherché ci-dessus est Ordering[A]
, où A
est un type réel, pas un paramètre de type: c'est un argument de type pour Ordering
. Voir la section 7.2 de la spécification Scala.
Ceci est disponible depuis Scala 2.8.0.
Objets externes pour les types imbriqués
Je n'en ai pas vu d'exemples. Je serais reconnaissant si quelqu'un pouvait en partager un. Le principe est simple:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
Autres dimensions
Je suis presque sûr que c'était une blague, mais cette réponse n'est peut-être pas à jour. Ne considérez donc pas cette question comme l'arbitre final de ce qui se passe, et si vous remarquez qu'elle est obsolète, veuillez m'en informer afin que je puisse y remédier.
ÉDITER
Questions d'intérêt connexes: