Scala Language
Typ klasser
Sök…
Anmärkningar
För att undvika serialiseringsproblem, särskilt i distribuerade miljöer (t.ex. Apache Spark ), är det en bästa praxis att implementera Serializable
drag för typklassinstanser.
Enkel typsklass
En typklass är helt enkelt ett trait
med en eller flera typparametrar:
trait Show[A] {
def show(a: A): String
}
Istället för att utöka en typsklass tillhandahålls en implicit instans av typsklassen för varje stödd typ. Att placera dessa implementeringar i det följeslagande objektet av typklassen gör att implicit upplösning kan fungera utan någon speciell import:
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
}
Om du vill garantera att en generisk parameter som skickas till en funktion har en instans av en typsklass, använd implicita parametrar:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
Du kan också använda en kontextbunden :
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Ring ovanstående log
som alla andra metoder. Det kommer inte att kompilera om en implicit Show[A]
-implementering inte kan hittas för den A
du skickar för att 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]]
Detta exempel implementerar klassen Show
typ. Detta är en vanlig typklass som används för att konvertera godtyckliga instanser av godtyckliga typer till String
. Även om varje objekt har en toString
metod är det inte alltid klart om toString
är definierat på ett användbart sätt. Med användning av klassen Show
typ kan du garantera att allt som skickas till log
har en väldefinierad konvertering till String
.
Utöka en typklass
I detta exempel diskuteras utvidgningen av klassen nedan.
trait Show[A] {
def show: String
}
För att göra en klass du kontrollerar (och är skriven i Scala) förlänga typklassen, lägg till ett implicit till dess följeslagareobjekt. Låt oss visa hur vi kan få Person
från detta exempel för att utöka Show
:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
Vi kan få denna klass att förlänga Show
genom att lägga till ett implicit till Person
följeslagareobjekt:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
Ett följeslagareobjekt måste finnas i samma fil som klassen, så du behöver både class Person
och object Person
i samma fil.
För att skapa en klass som du inte kontrollerar eller inte är skriven i Scala, utöka typklassen, lägg till ett implicit till följeslagareobjektet till typklassen, som visas i exemplet Simple Type Class .
Om du varken kontrollerar klassen eller typklassen, skapa ett implicit som överallt och import
den. Använda log
i exemplet 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) }
}
Lägg till typklassfunktioner till typer
Skalas implementering av typklasser är snarare ordbok. Ett sätt att minska verbositeten är att införa så kallade "Operation Classes". Dessa klasser slår automatiskt in en variabel / värde när de importeras för att utöka funktionaliteten.
För att illustrera detta, låt oss först skapa en enkel typklass:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
Nästa kommer vi att implementera egenskapen (initiera typklassen):
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
}
}
För tillfället skulle det vara mycket besvärligt att använda våra inställningsbara instanser:
import Instances._
val three = addableInt.add(1,2)
Vi vill hellre bara skriva skriva 1.add(2)
. Därför skapar vi en "Operation Class" (även kallad en "Ops Class") som alltid kommer att svepa över en typ som implementerar Addable
.
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
Nu kan vi använda vår nya funktion add
som om den var en del av Int
och 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" -klasser kan skapas automatiskt med makron i simulacrum- biblioteket:
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}