J'ai vu une fonction nommée implicitly
utilisée dans des exemples Scala. Qu'est-ce que c'est et comment est-elle utilisée?
scala> sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
| implicit def stringImpl = new Foo[String] {
| def apply(list : List[String]) = println("String")
| }
| implicit def intImpl = new Foo[Int] {
| def apply(list : List[Int]) = println("Int")
| }
| } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit
scala> foo(1)
<console>:8: error: type mismatch;
found : Int(1)
required: List[?]
foo(1)
^
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:8: error: could not find implicit value for evidence parameter of type
Foo[Double]
foo(List(1.0))
^
Notez que nous devons écrire implicitly[Foo[A]].apply(x)
puisque le compilateur pense que implicitly[Foo[A]](x)
signifie que nous appelons implicitly
avec des paramètres.
Voir aussi Comment étudier les objets/types/etc de Scala REPL? et Où fait Scala cherche-t-il implicite?
Voici quelques raisons d'utiliser la méthode délicieusement simple implicitly
.
Une vue implicite peut être déclenchée lorsque le préfixe d'une sélection (par exemple, the.prefix.selection(args)
ne contient pas de membre selection
applicable à args
(même après une tentative de convert args
avec vues implicites). Dans ce cas, le compilateur recherche les membres implicites, définis localement dans les portées actuelles ou englobantes, hérités ou importés, qui sont des fonctions du type de cette the.prefix
à un type avec selection
défini ou à des méthodes implicites équivalentes.
scala> 1.min(2) // Int doesn't have min defined, where did that come from?
res21: Int = 1
scala> implicitly[Int => { def min(i: Int): Any }]
res22: (Int) => AnyRef{def min(i: Int): Any} = <function1>
scala> res22(1) //
res23: AnyRef{def min(i: Int): Int} = 1
scala> .getClass
res24: Java.lang.Class[_] = class scala.runtime.RichInt
Des vues implicites peuvent également être déclenchées lorsqu'une expression n'est pas conforme au type attendu, comme ci-dessous:
scala> 1: scala.runtime.RichInt
res25: scala.runtime.RichInt = 1
Ici, le compilateur recherche cette fonction:
scala> implicitly[Int => scala.runtime.RichInt]
res26: (Int) => scala.runtime.RichInt = <function1>
Les paramètres implicites sont sans doute une caractéristique plus importante de Scala que les vues implicites. Ils supportent le modèle de classe de type. La bibliothèque standard l'utilise à quelques endroits - voir scala.Ordering
et comment il est utilisé dans SeqLike#sorted
. Les paramètres implicites sont également utilisés pour transmettre les manifestes Array et les instances CanBuildFrom
.
Scala 2.8 autorise une syntaxe abrégée pour les paramètres implicites, appelée Context Bounds. En bref, une méthode avec un paramètre de type A
qui nécessite un paramètre implicite de type M[A]
:
def foo[A](implicit ma: M[A])
peut être réécrit comme:
def foo[A: M]
Mais à quoi sert-il de passer le paramètre implicite sans le nommer? Comment cela peut-il être utile lors de l’implémentation de la méthode foo
?
Souvent, il n'est pas nécessaire de faire directement référence au paramètre implicite, il sera utilisé comme un argument implicite pour une autre méthode appelée. Si cela est nécessaire, vous pouvez toujours conserver la signature de la méthode abrégée avec le lien de contexte et appeler implicitly
pour matérialiser la valeur:
def foo[A: M] = {
val ma = implicitly[M[A]]
}
Supposons que vous appeliez une méthode qui imprime joliment une personne, en utilisant une approche basée sur une classe de type:
trait Show[T] { def show(t: T): String }
object Show {
implicit def IntShow: Show[Int] = new Show[Int] { def show(i: Int) = i.toString }
implicit def StringShow: Show[String] = new Show[String] { def show(s: String) = s }
def ShoutyStringShow: Show[String] = new Show[String] { def show(s: String) = s.toUpperCase }
}
case class Person(name: String, age: Int)
object Person {
implicit def PersonShow(implicit si: Show[Int], ss: Show[String]): Show[Person] = new Show[Person] {
def show(p: Person) = "Person(name=" + ss.show(p.name) + ", age=" + si.show(p.age) + ")"
}
}
val p = Person("bob", 25)
implicitly[Show[Person]].show(p)
Que faire si nous voulons changer la façon dont le nom est sorti? Nous pouvons explicitement appeler PersonShow
, explicitement passer une alternative Show[String]
, mais nous voulons que le compilateur transmette le Show[Int]
.
Person.PersonShow(si = implicitly, ss = Show.ShoutyStringShow).show(p)
Implicitly
est disponible dans Scala 2.8 et est défini dans Predef comme:
def implicitly[T](implicit e: T): T = e
Il est couramment utilisé pour vérifier si un valeur de type T
implicite est disponible et le renvoyer si tel est le cas.
Exemple simple tiré de présentation de retronym :
scala> implicit val a = "test" // define an implicit value of type String
a: Java.lang.String = test
scala> val b = implicitly[String] // search for an implicit value of type String and assign it to b
b: String = test
scala> val c = implicitly[Int] // search for an implicit value of type Int and assign it to c
<console>:6: error: could not find implicit value for parameter e: Int
val c = implicitly[Int]
^
Une réponse "apprend à pêcher" consiste à utiliser l’index alphabétique des membres actuellement disponible dans les nightlies Scaladoc . Les lettres (et le #
, pour les noms non alphabétiques) en haut du volet package/classe sont des liens vers l'index pour les noms de membres commençant par cette lettre (pour toutes les classes). Si vous choisissez I
, par exemple, vous trouverez l'entrée implicitly
avec une occurrence, dans Predef
, que vous pouvez visiter à partir du lien situé à cet emplacement.