D'où vient Scala recherchez implicites?

Un implicite question pour les nouveaux arrivants à la Scala semble être: d'où vient le compilateur recherchez implicites? Je veux dire implicite parce que la question ne semble jamais être complètement formée, comme s'il n'y avait pas de mots pour elle. :- ) Par exemple, d'où viennent les valeurs pour integral ci-dessous?

scala> import scala.math._
import scala.math._

scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit

scala> foo(0)
scala.math.Numeric$IntIsIntegral$@3dbea611

scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@48c610af

une autre question qui fait suite à ceux qui décident d'apprendre la réponse à la première question Est comment le compilateur choisit-il implicite à utiliser, dans certaines situations d'apparente ambiguïté (mais qui compilent quand même)?

par exemple, scala.Predef définit deux conversions de String : une à WrappedString et une autre à StringOps . Les deux classes, cependant, partagent beaucoup de méthodes, alors pourquoi Scala ne se plaint pas de l'ambiguïté quand, disons, appeler map ?

Note: cette question a été inspirée par cette autre question , dans l'espoir d'exposer le problème de manière plus générale. L'exemple a été copié de là, parce qu'il est mentionné dans la réponse.

371
demandé sur Community 2011-04-08 20:29:37

2 réponses

Types d'implications

implique dans Scala se réfère soit à une valeur qui peut être transmise "automatiquement", pour ainsi dire, ou à une conversion d'un type à un autre qui est faite automatiquement.

Conversion Implicite

parlant très brièvement de ce dernier type, si l'on appelle une méthode m sur un objet o d'une classe C , et que cette classe ne supporte pas la méthode m , alors Scala cherchera une conversion implicite de C en quelque chose que fait support m . Un exemple simple serait la méthode map sur String :

"abc".map(_.toInt)

String ne supporte pas la méthode map , mais StringOps , et il y a une conversion implicite de String en StringOps disponible (voir implicit def augmentString sur Predef ).

Paramètres Implicites

L'autre implicite est l'implicite paramètre . Ceux-ci sont passés aux appels de méthode comme n'importe quel autre paramètre, mais le compilateur essaie de les remplir automatiquement. S'il ne peut pas, il se plaindra. Un peut passer ces paramètres explicitement , qui est la façon dont on utilise breakOut , par exemple (voir la question au sujet de breakOut , un jour que vous vous sentez vers le haut pour un défi).

dans ce cas, il faut déclarer la nécessité d'une déclaration implicite, telle que la foo 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, à condition qu'il y ait une conversion implicite disponible de sa classe en Seq[T] . Pour cette raison , je peux passer un String à getIndex , et ça va marcher.

dans les coulisses, le compilateur change seq.IndexOf(value) en conv(seq).indexOf(value) .

C'est tellement utile qu'il n'y a sucre syntaxique pour les écrire. En utilisant ce sucre syntactique, getIndex peut être défini comme ceci:

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)

Ce sucre syntaxique est décrit comme un afficher lié , qui s'apparente à un limite supérieure ( CC <: Seq[Int] ) ou une limite inférieure ( T >: Null ).

Contexte Limites

un autre motif courant dans les paramètres implicites est le type class pattern . Ce schéma permet de fournir des interfaces communes aux classes qui ne les ont pas déclarées. Il peut à la fois servir de modèle de pont-gagner la séparation des préoccupations-et comme modèle d'adaptateur.

la classe Integral 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 y a une bibliothèque qui utilise beaucoup Ce modèle, appelé Scalaz.

ceci est 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é lié au contexte , qui est rendu moins utile par la nécessité de se référer à l'implicite. Un la 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 quand vous avez juste besoin de passer à d'autres méthodes qui les utilisent. Par exemple, la méthode sorted sur Seq nécessite un Ordering implicite . Pour créer une méthode reverseSort , on pourrait écrire:

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

Parce que Ordering[T] a été implicitement passé à reverseSort , il peut alors passer implicitement à sorted .

D'où viennent les Implicits?

quand 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 conviendra au besoin.

cette recherche obéit à certaines règles qui définissent les implicits qui sont visibles et ceux qui ne le sont pas. Le tableau suivant montrant où le compilateur va chercher les implicits a été pris d'un excellent présentation sur les implicits par Josh Suereth, que je recommande vivement à tous ceux qui veulent améliorer leur connaissance du Scala. Il a été complété depuis par des commentaires et des mises à jour.

les implicits disponibles sous le numéro 1 ci-dessous ont priorité sur ceux du numéro 2. Autre que cela, s'il y a plusieurs arguments admissibles qui correspondent au type du paramètre implicite, un plus spécifique sera choisi en utilisant les règles de résolution de la surcharge statique (voir Spécification Scala §6.26.3). Des renseignements plus détaillés se trouvent dans une question à laquelle je renvoie à la fin de la présente réponse.

  1. premier regard dans le champ d'application actuel
    • Implicites définies dans le champ d'application actuel
    • importations explicites
    • "15191670920 caractères de remplacement des importations
    • même domaine dans d'autres fichiers
  2. Maintenant, regardez les types associés dans
    • Compagnon des objets de type
    • portée implicite du type d'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 quelques exemples pour eux:

Implicites Définies dans le Champ d'application Actuel

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
"15191000920 Caractères De Remplacement Des Importations
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 les autres fichiers

Modifier : Il semble ce n'est pas une autre priorité. 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 usage. Voir aussi comment package objects peut être utilisé pour introduire des implicits.

Compagnon des Objets de Type

Il y a deux objets compagnons de note ici. Tout d'abord, le compagnon objet du type "source" est examiné. Par exemple , à l'intérieur de l'objet Option il y a une conversion implicite en Iterable , de sorte que l'on peut appeler Iterable méthodes sur Option , ou passer Option à quelque chose s'attendant à 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 , ce qui Option n'est pas. Le compilateur regarde ensuite à l'intérieur de Option 's object companion et trouve la conversion en Iterable , qui est un TraversableOnce , ce qui rend cette expression correcte.

Deuxièmement, le compagnon de l'objet du type attendu:

List(1, 2, 3).sorted

la méthode sorted prend un Ordering implicite . Dans ce cas, il regarde à l'intérieur de l'objet Ordering , compagnon de la la classe Ordering , et 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é les implicites Numeric[Int] et Numeric[Long] dans votre question, soit dit en passant, comme ils se trouvent à l'intérieur de Numeric , et non Integral .

portà © e implicite du type D'Argument

Si vous avez un méthode avec un type d'argument A , puis la portée implicite de type A sera également considérée. Par "portée implicite", je veux dire que toutes ces règles seront appliquées de façon récursive -- par exemple, l'objet compagnon de A sera recherché pour les implicits, 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. 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)

Disponible depuis Scala 2.9.1.

implicite Scope of Type Arguments

cela est nécessaire pour que le modèle de classe de type Fonctionne vraiment. Prenons Ordering , par exemple: il est livré avec quelques implicits dans son objet compagnon, mais vous ne pouvez pas y ajouter des choses. Alors comment pouvez-vous faire un Ordering pour votre propre classe qui se trouve automatiquement?

commençons par la mise en œuvre:

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, pensez à ce qui se passe quand vous appelez

List(new A(5), new A(2)).sorted

comme nous l'avons vu, la méthode sorted s'attend à un Ordering[A] (en fait, il s'attend à un Ordering[B] , où B >: A ). Il n'y a rien de tel à l'intérieur de Ordering , et il n'y a pas de type "source" sur lequel regarder. Évidemment, il le trouve à l'intérieur de A , qui est un argument de type de Ordering .

C'est aussi de cette façon que diverses méthodes de collecte s'attendent à ce que CanBuildFrom fonctionne: les implicits se trouvent à l'intérieur des objets companions aux paramètres de type de CanBuildFrom .

Note : Ordering est défini comme trait Ordering[T] , où T est un paramètre de type. Précédemment, J'ai dit que Scala a regardé à l'intérieur des paramètres de type, ce qui n'a pas beaucoup de sens. La recherche implicite ci-dessus est Ordering[A] , où A est un type réel, pas un paramètre de type: c'est un type argument à Ordering . Voir la section 7.2 de la spécification Scala.

Disponible depuis Scala 2.8.0.

objets extérieurs pour les types imbriqués

Je n'en ai pas vu d'exemples. Je serais reconnaissant si quelqu'un pouvait partager. 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 sûr que c'était une blague, mais cela pourrait ne pas être à jour. Alors ne prenez pas cette question comme étant l'arbitre final de ce qui se passe, et si vous remarquez qu'elle est périmée, s'il vous plaît, informez-moi pour que je puisse la réparer.

MODIFIER

questions connexes d'intérêt:

527
répondu Daniel C. Sobral 2018-01-25 15:30:24

j'ai voulu découvrir la priorité de la résolution de paramètre implicite, pas seulement où il cherche, donc j'ai écrit un post de blog revisiter implicits without import tax (et priorité de paramètre implicite encore une fois après quelques commentaires).

Voici la liste:

  • 1) implique visible à l'invocation champ d'application actuel via déclaration locale, importations, champ d'application externe, héritage, paquet objet accessible sans préfixe.
  • 2) implicit scope , qui contient toutes sortes d'objets compagnons et d'objets paquet qui portent une certaine relation au type implicite que nous recherchons (c.-à-d. l'objet paquet du type, l'objet compagnon du type lui-même, de son constructeur de type s'il y en a, de ses paramètres s'il y en a, et aussi de son supertype et supertraits).

Si à chaque étape, nous trouvons plus de une règle implicite de surcharge statique est utilisée pour la résoudre.

24
répondu Eugene Yokota 2012-01-08 18:39:38