De nombreux développeurs débutants et non Scala considÚrent les implicites comme une fonctionnalité modérément utile. L'utilisation est généralement limitée au passage ExecutionContext
Ă Future
. D'autres évitent l'implicite et considÚrent l'opportunité comme nuisible.
Un code comme celui-ci fait peur Ă beaucoup de gens:
implicit def function(implicit argument: A): B
Mais je pense que ce mécanisme est un avantage important de la langue, voyons pourquoi.
En bref sur les implicites
En général, implicits est un mécanisme de complétion automatique du code lors de la compilation:
pour les arguments implicites, la valeur est automatiquement substituée
pour les conversions implicites, la valeur est automatiquement encapsulée dans un appel de méthode
Je n'entrerai pas dans ce sujet, qui sont intéressés à regarder cette vidéo du créateur de la langue . En bref, ce mécanisme est utilisé dans plusieurs cas différents (et dans Scala 3 sera divisé en plusieurs fonctionnalités distinctes):
Passer un contexte implicite (comme
ExecutionContext
)
Conversions implicites (obsolĂštes)
Méthodes d'extension (sucre syntaxique pour ajouter des méthodes à des types existants)
Classes de type et résolution implicite
Classes de type et inférence implicite
En elles-mĂȘmes, les classes de types n'apportent aucune nouveautĂ© ou rĂ©volution - elles sont simplement l'implĂ©mentation de mĂ©thodes "pour" le type, et non "dans" le type lui-mĂȘme, c'est comme la diffĂ©rence entre Comparable
& Ordering
( Comparator
en Java):
Comparable
:
class Person(age: Int) extends Comparable[Person] {
override def compareTo(o: Person): Int = age compareTo o.age
}
def max[T <: Comparable[T]](xs: Iterable[T]): T = xs.reduce[T] {
case (a, b) if (a compareTo b) < 0 => b
case (a, _) => a
}
Ordering
, :
case class Person(age: Int)
implicit object ByAgeOrdering extends Ordering[Person] {
override def compare(o1: Person, o2: Person): Int = o1.age compareTo o2.age
}
def max[T: Ordering](xs: Iterable[T]): T = xs.reduce[T] {
case (a, b) if Ordering[T].lt(a, b) => b
case (a, _) => a
}
// is syntactic sugar for
def max[T](xs: Iterable[T])(implicit evidence: Ordering[T]): T = ...
.
. :
implicit val value: A = ???
implicit def definition: B = ???
implicit def conversion(argument: C): D = ???
implicit def function(implicit argument: E): F = ???
? conversion
, , : value
, definition
& function
. : val
, def
. â .
â , , .
â , â , :
implicit def pairOrder[A: Ordering, B: Ordering]: Ordering[(A, B)] = {
case ((a1, b1), (a2, b2)) if Ordering[A].equiv(a1, a2) => Ordering[B].compare(b1, b2)
case ((a1, _), (a2, _)) => Ordering[A].compare(a1, a2)
}
// again, just syntactic sugar for:
implicit def pairOrder[A, B](implicit a: Ordering[A], b: Ordering[B]): Ordering[(A, B)] = ...
, :
val values = Seq(
(Person(30), ("A", "A")),
(Person(30), ("A", "B")),
(Person(20), ("A", "C"))
)
max(values) // => (Person(30),(A,B))
Seq[(Person, (String, String))]
Ordering
:
max(values)(
pairOrder(
ByAgeOrdering,
pairOrder(Ordering.String, Ordering.String)
)
)
Ainsi, l'infĂ©rence implicite vous permet de dĂ©crire des rĂšgles d'infĂ©rence gĂ©nĂ©rales et de demander au compilateur de combiner ces rĂšgles ensemble et d'obtenir une implĂ©mentation spĂ©cifique de la classe de type. L'ajout de votre propre type ou de vos propres rĂšgles n'a pas besoin de tout dĂ©crire depuis le dĂ©but - le compilateur combinera tout lui-mĂȘme pour obtenir l'objet souhaitĂ©.
Et surtout, si le compilateur échoue, vous recevrez une erreur de compilation, pas une erreur d'exécution, et vous pourrez résoudre le problÚme immédiatement. Bien que, bien sûr, il y ait une mouche dans la pommade dans la pommade - si le compilateur a échoué, vous ne savez pas quel lien dans la chaßne manquait - il n'est pas toujours facile de déboguer cela.
Espérons que l'implicite est maintenant un peu plus explicite.