Scala Language
Classes de types
Recherche…
Remarques
Pour éviter les problèmes de sérialisation, en particulier dans les environnements distribués (par exemple, Apache Spark ), il est recommandé d'implémenter le trait Serializable
pour les instances de classe de type.
Classe de type simple
Une classe de type est simplement un trait
avec un ou plusieurs paramètres de type:
trait Show[A] {
def show(a: A): String
}
Au lieu d'étendre une classe de type, une instance implicite de la classe de type est fournie pour chaque type pris en charge. Placer ces implémentations dans l'objet compagnon de la classe de type permet une résolution implicite pour fonctionner sans importations spéciales:
object Show {
implicit val intShow: Show[Int] = new Show {
def show(x: Int): String = x.toString
}
implicit val dateShow: Show[java.util.Date] = new Show {
def show(x: java.util.Date): String = x.getTime.toString
}
// ..etc
}
Si vous voulez garantir qu'un paramètre générique transmis à une fonction a une instance d'une classe de type, utilisez des paramètres implicites:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
Vous pouvez également utiliser un contexte lié :
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Appelez la méthode de log
ci-dessus comme toute autre méthode. Il ne parviendra pas à compiler si un implicite Show[A]
la mise en œuvre ne peut être trouvée pour l' A
vous passez à vous log
log(10) // prints: "10"
log(new java.util.Date(1469491668401L) // prints: "1469491668401"
log(List(1,2,3)) // fails to compile with
// could not find implicit value for evidence parameter of type Show[List[Int]]
Cet exemple implémente la classe de type Show
. C'est une classe de type commune utilisée pour convertir des instances arbitraires de types arbitraires en String
s. Même si chaque objet a une méthode toString
, il n'est pas toujours clair si toString
est défini de manière utile. Avec l'utilisation de la classe de type Show
, vous pouvez garantir que tout élément transmis à log
a une conversion bien définie en String
.
Extension d'une classe de type
Cet exemple traite de l'extension de la classe de type ci-dessous.
trait Show[A] {
def show: String
}
Pour rendre une classe que vous contrôlez (et est écrite en Scala), étendez la classe de type, ajoutez un implicite à son objet compagnon. Montrons comment nous pouvons obtenir la classe Person
de cet exemple pour étendre Show
:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
Nous pouvons faire en sorte que cette classe étende Show
en ajoutant un objet implicite à l'objet compagnon de Person
:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
Un objet compagnon doit être dans le même fichier que la classe, vous avez donc besoin à la fois class Person
et d' object Person
dans le même fichier.
Pour créer une classe que vous ne contrôlez pas ou qui n'est pas écrite en Scala, étendez la classe de type, ajoutez un implicite à l'objet compagnon de la classe de type, comme indiqué dans l'exemple de classe de type simple .
Si vous ne contrôlez ni la classe ni la classe de type, créez un fichier implicite comme ci-dessus et import
le. En utilisant la méthode log
sur l' exemple de type Simple type :
object MyShow {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
def logPeople(persons: Person*): Unit = {
import MyShow.personShow
persons foreach { p => log(p) }
}
Ajouter des fonctions de classe de type aux types
L'implémentation Scala des classes de type est plutôt verbeuse. Une façon de réduire la verbosité consiste à introduire des "classes d'opération". Ces classes encapsulent automatiquement une variable / valeur lorsqu'elles sont importées pour étendre les fonctionnalités.
Pour illustrer cela, commençons par créer une classe de type simple:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
Ensuite, nous allons implémenter le trait (instancier la classe de type):
object Instances {
// Instance for Int
// Also called evidence object, meaning that this object saw that Int learned how to be added
implicit object addableInt extends Addable[Int] {
def add(x: Int, y: Int): Int = x + y
}
// Instance for String
implicit object addableString extends Addable[String] {
def add(x: String, y: String): String = x + y
}
}
Pour le moment, il serait très compliqué d’utiliser nos instances Addable:
import Instances._
val three = addableInt.add(1,2)
Nous préférerions simplement écrire write 1.add(2)
. Par conséquent, nous allons créer une "Classe d'opération" (également appelée "Classe Ops") qui Addable
toujours un type qui implémente Addable
.
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
Maintenant, nous pouvons utiliser notre nouvelle fonction add
comme si elle faisait partie de Int
et String
:
object Main {
import Instances._ // import evidence objects into this scope
import Ops._ // import the wrappers
def main(args: Array[String]): Unit = {
println(1.add(5))
println("mag".add("net"))
// println(1.add(3.141)) // Fails because we didn't create an instance for Double
}
}
Les classes "Ops" peuvent être créées automatiquement par les macros de la bibliothèque simulacrum :
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}