Scala Language
Type klassen
Zoeken…
Opmerkingen
Om problemen met serialisatie te voorkomen, met name in gedistribueerde omgevingen (bijvoorbeeld Apache Spark ), is het een goede gewoonte om de eigenschap Serializable
te implementeren voor instanties van type klasse.
Eenvoudige typeklasse
Een typeklasse is gewoon een trait
met een of meer typeparameters:
trait Show[A] {
def show(a: A): String
}
In plaats van een typeklasse uit te breiden, wordt voor elk ondersteund type een impliciete instantie van de typeklasse verstrekt. Door deze implementaties in het bijbehorende object van de type-klasse te plaatsen, kan impliciete oplossing werken zonder speciale invoer:
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
}
Als u wilt garanderen dat een generieke parameter die aan een functie wordt doorgegeven, een instantie van een typeklasse heeft, gebruikt u impliciete parameters:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
U kunt ook een contextgebonden context gebruiken :
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Roep de bovenstaande log
zoals elke andere methode. Het kan niet worden gecompileerd als er geen impliciete Show[A]
-implementatie kan worden gevonden voor de A
u wilt 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]]
In dit voorbeeld wordt de klasse Show
type geïmplementeerd. Dit is een algemene typeklasse die wordt gebruikt om willekeurige instanties van willekeurige typen om te zetten in String
s. Hoewel elk object een toString
methode heeft, is het niet altijd duidelijk of toString
op een nuttige manier is gedefinieerd. Met behulp van de klasse Show
type kunt u garanderen dat alles wat wordt doorgegeven aan het log
een goed gedefinieerde conversie naar String
.
Een typeklasse uitbreiden
In dit voorbeeld wordt de uitbreiding van de onderstaande typeklasse besproken.
trait Show[A] {
def show: String
}
Als u een klasse wilt maken die u bestuurt (en in Scala is geschreven), breidt u de typeklasse uit, voegt u een impliciet toe aan het bijbehorende object. Laten we laten zien hoe we de Person
klasse uit dit voorbeeld kunnen krijgen om Show
uit te breiden:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
We kunnen deze klasse uit te breiden Show
door het toevoegen van een impliciet naar Person
metgezel object 's:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
Een bijbehorend object moet zich in hetzelfde bestand bevinden als de klasse, dus u moet zowel de class Person
als de object Person
in hetzelfde bestand hebben.
Als u een klasse wilt maken die u niet beheert of niet in Scala is geschreven, breidt u de type-klasse uit, voegt u een impliciet toe aan het bijbehorende object van de type-klasse, zoals weergegeven in het voorbeeld van de Simple Type Class .
Als u noch de klasse, noch de typeklasse beheert, maakt u een impliciet zoals hierboven en import
het. Met behulp van de log
in het voorbeeld van Simple Type Class :
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) }
}
Voeg typeklassefuncties toe aan typen
Scala's implementatie van typeklassen is vrij uitgebreid. Een manier om de verbosity te verminderen is door zogenaamde "Operation Classes" te introduceren. Deze klassen verpakken automatisch een variabele / waarde wanneer ze worden geïmporteerd om de functionaliteit uit te breiden.
Om dit te illustreren, laten we eerst een eenvoudige typeklasse maken:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
Vervolgens zullen we de eigenschap implementeren (de typeklasse instantiëren):
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
}
}
Op dit moment zou het erg omslachtig zijn om onze Addable-instanties te gebruiken:
import Instances._
val three = addableInt.add(1,2)
We schrijven liever schrijven 1.add(2)
. Daarom maken we een "Operation Class" (ook wel een "Ops Class" genoemd) die altijd een type Addable
dat Addable
implementeert.
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
Nu kunnen we onze nieuwe functie add
alsof het onderdeel was van Int
en 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
}
}
"Ops" -klassen kunnen automatisch door macro's in de simulacrumbibliotheek worden gemaakt:
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}