Scala Language
Implicits
Recherche…
Syntaxe
- val implicite x: T = ???
Remarques
Les classes implicites permettent d'ajouter des méthodes personnalisées aux types existants, sans avoir à modifier leur code, ce qui enrichit les types sans avoir à contrôler le code.
L'utilisation de types implicites pour enrichir une classe existante est souvent appelée modèle «enrichir ma bibliothèque».
Restrictions sur les classes implicites
- Les classes implicites ne peuvent exister que dans une autre classe, objet ou trait.
- Les classes implicites ne peuvent avoir qu'un seul paramètre constructeur non implicite.
- Il se peut qu'il n'y ait pas d'autre définition d'objet, de classe, de trait ou de membre de classe dans la même portée qui porte le même nom que la classe implicite.
Conversion implicite
Une conversion implicite permet au compilateur de convertir automatiquement un objet d'un type en un autre. Cela permet au code de traiter un objet comme un objet d'un autre type.
case class Foo(i: Int)
// without the implicit
Foo(40) + 2 // compilation-error (type mismatch)
// defines how to turn a Foo into an Int
implicit def fooToInt(foo: Foo): Int = foo.i
// now the Foo is converted to Int automatically when needed
Foo(40) + 2 // 42
La conversion est à sens unique: dans ce cas, vous ne pouvez pas convertir 42
en Foo(42)
. Pour ce faire, une deuxième conversion implicite doit être définie:
implicit def intToFoo(i: Int): Foo = Foo(i)
Notez que c'est le mécanisme par lequel une valeur flottante peut être ajoutée à une valeur entière, par exemple.
Les conversions implicites doivent être utilisées avec parcimonie car elles masquent ce qui se passe. Il est recommandé d'utiliser une conversion explicite via un appel de méthode, sauf s'il existe un gain de lisibilité tangible lié à l'utilisation d'une conversion implicite.
Il n'y a pas d'impact significatif sur les performances des conversions implicites.
Scala importe automatiquement diverses conversions implicites dans scala.Predef
, y compris toutes les conversions de Java vers Scala et scala.Predef
. Ceux-ci sont inclus par défaut dans toute compilation de fichiers.
Paramètres implicites
Les paramètres implicites peuvent être utiles si un paramètre d'un type doit être défini une fois dans la portée, puis appliqué à toutes les fonctions qui utilisent une valeur de ce type.
Un appel de fonction normal ressemble à ceci:
// import the duration methods
import scala.concurrent.duration._
// a normal method:
def doLongRunningTask(timeout: FiniteDuration): Long = timeout.toMillis
val timeout = 1.second
// timeout: scala.concurrent.duration.FiniteDuration = 1 second
// to call it
doLongRunningTask(timeout) // 1000
Maintenant, disons que nous avons des méthodes qui ont toutes une durée de temporisation, et nous voulons appeler toutes ces méthodes en utilisant le même délai. Nous pouvons définir le délai d'attente comme une variable implicite.
// import the duration methods
import scala.concurrent.duration._
// dummy methods that use the implicit parameter
def doLongRunningTaskA()(implicit timeout: FiniteDuration): Long = timeout.toMillis
def doLongRunningTaskB()(implicit timeout: FiniteDuration): Long = timeout.toMillis
// we define the value timeout as implicit
implicit val timeout: FiniteDuration = 1.second
// we can now call the functions without passing the timeout parameter
doLongRunningTaskA() // 1000
doLongRunningTaskB() // 1000
La façon dont cela fonctionne est que le compilateur scalac recherche une valeur dans l'étendue qui est marquée comme implicite et dont le type correspond à celui du paramètre implicite. S'il en trouve un, il l'appliquera comme paramètre implicite.
Notez que cela ne fonctionnera pas si vous définissez deux implicits, voire plus, du même type dans la portée.
Pour personnaliser le message d'erreur, utilisez l'annotation implicitNotFound
sur le type:
@annotation.implicitNotFound(msg = "Select the proper implicit value for type M[${A}]!")
case class M[A](v: A) {}
def usage[O](implicit x: M[O]): O = x.v
//Does not work because no implicit value is present for type `M[Int]`
//usage[Int] //Select the proper implicit value for type M[Int]!
implicit val first: M[Int] = M(1)
usage[Int] //Works when `second` is not in scope
implicit val second: M[Int] = M(2)
//Does not work because more than one implicit values are present for the type `M[Int]`
//usage[Int] //Select the proper implicit value for type M[Int]!
Un timeout est un cas d'utilisation habituel pour cela, ou par exemple dans Akka, ActorSystem est (la plupart du temps) toujours le même, donc il est généralement passé implicitement. Une autre utilisation serait la librairie, le plus souvent avec les bibliothèques FP qui reposent sur des classes de types (comme scalaz , chats ou ravissement ).
Il est généralement considéré comme une mauvaise pratique d'utiliser des paramètres implicites avec des types de base comme Int , Long , String, etc., car cela créerait de la confusion et rendrait le code moins lisible.
Classes implicites
Les classes implicites permettent d'ajouter de nouvelles méthodes à des classes précédemment définies.
La classe String
n'a pas de méthode withoutVowels
. Cela peut être ajouté comme ça:
object StringUtil {
implicit class StringEnhancer(str: String) {
def withoutVowels: String = str.replaceAll("[aeiou]", "")
}
}
La classe implicite a un seul paramètre constructeur ( str
) avec le type que vous souhaitez étendre ( String
) et contient la méthode que vous souhaitez "ajouter" au type ( withoutVowels
). Les méthodes nouvellement définies peuvent désormais être utilisées directement sur le type amélioré (lorsque le type amélioré est implicite):
import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope
println("Hello world".withoutVowels) // Hll wrld
Sous le capot, les classes implicites définissent une conversion implicite du type amélioré à la classe implicite, comme ceci:
implicit def toStringEnhancer(str: String): StringEnhancer = new StringEnhancer(str)
Les classes implicites sont souvent définies en tant que classes Value pour éviter de créer des objets d'exécution et supprimer ainsi la surcharge d'exécution:
implicit class StringEnhancer(val str: String) extends AnyVal {
/* conversions code here */
}
Avec la définition améliorée ci-dessus, une nouvelle instance de StringEnhancer
n'a pas besoin d'être créée chaque fois que la méthode withoutVowels
est appelée.
Résolution des paramètres implicites Utilisation de 'implicitement'
En supposant une liste de paramètres implicite avec plusieurs paramètres implicites:
case class Example(p1:String, p2:String)(implicit ctx1:SomeCtx1, ctx2:SomeCtx2)
Maintenant, en supposant que l'une des instances implicites n'est pas disponible ( SomeCtx1
) alors que toutes les autres instances implicites nécessaires sont dans la portée, pour créer une instance de la classe, une instance de SomeCtx1
doit être fournie.
Cela peut être fait en préservant chaque autre instance implicite dans la portée en utilisant le mot-clé implicitly
:
Example("something","somethingElse")(new SomeCtx1(), implicitly[SomeCtx2])
Implique dans le REPL
Pour afficher tous les implicits
dans la portée d'une session REPL:
scala> :implicits
Inclure également les conversions implicites définies dans Predef.scala
:
scala> :implicits -v
Si on a une expression et que l'on souhaite voir l'effet de toutes les règles de réécriture qui s'y appliquent (y compris les implicits):
scala> reflect.runtime.universe.reify(expr) // No quotes. reify is a macro operating directly on code.
(Exemple:
scala> import reflect.runtime.universe._
scala> reify(Array("Alice", "Bob", "Eve").mkString(", "))
resX: Expr[String] = Expr[String](Predef.refArrayOps(Array.apply("Alice", "Bob", "Eve")(Predef.implicitly)).mkString(", "))
)