Scala Language
Classi di tipo
Ricerca…
Osservazioni
Per evitare problemi di serializzazione, in particolare in ambienti distribuiti (es. Apache Spark ), è buona pratica implementare il tratto Serializable
per le istanze di classe type.
Classe di tipo semplice
Una classe di tipo è semplicemente un trait
con uno o più parametri di tipo:
trait Show[A] {
def show(a: A): String
}
Invece di estendere una classe di tipo, viene fornita un'istanza implicita della classe type per ogni tipo supportato. Il posizionamento di queste implementazioni nell'oggetto associato della classe del tipo consente di eseguire la risoluzione implicita senza importazioni speciali:
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
}
Se si desidera garantire che un parametro generico passato a una funzione abbia un'istanza di una classe di tipo, utilizzare i parametri impliciti:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
Puoi anche utilizzare un contesto :
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Chiama il metodo di log
sopra come qualsiasi altro metodo. Non riuscirà a compilare se non è possibile trovare un'implementazione implicita Show[A]
per A
si passa al 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]]
Questo esempio implementa la classe Show
type. Questa è una classe di tipo comune utilizzata per convertire istanze arbitrarie di tipi arbitrari in String
s. Anche se ogni oggetto ha un metodo toString
, non è sempre chiaro se toString
sia definito o meno in un modo utile. Con l'uso della classe Show
type, puoi garantire che tutto ciò che è passato al log
abbia una conversione ben definita in String
.
Estendere una classe di tipo
Questo esempio discute l'estensione della classe del tipo sottostante.
trait Show[A] {
def show: String
}
Per rendere una classe che controlli (ed è scritta in Scala) estendi la classe del tipo, aggiungi un implicito al suo oggetto compagno. Cerchiamo di mostrare come possiamo ottenere la classe Person
da questo esempio per estendere Show
:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
Possiamo rendere questa classe estendere Show
aggiungendo un implicito all'oggetto companion di Person
:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
Un oggetto complementare deve trovarsi nello stesso file della classe, quindi è necessario che sia la class Person
sia la class Person
object Person
nello stesso file.
Per creare una classe che non controlli, o che non sia scritta in Scala, estendi la classe del tipo, aggiungi un implicito all'oggetto complementare della classe type, come mostrato nell'esempio della classe di tipo semplice .
Se non controlli né la classe né la classe del tipo, crea un implicito come sopra ovunque e import
. Utilizzando il metodo log
sull'esempio della classe 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) }
}
Aggiungi le funzioni della classe tipo ai tipi
L'implementazione di Scala delle classi di tipi è piuttosto dettagliata. Un modo per ridurre la verbosità è introdurre le cosiddette "Classi di operazione". Queste classi comprimono automaticamente una variabile / valore quando vengono importate per estendere la funzionalità.
Per illustrare questo, cerchiamo innanzitutto di creare una classe di tipo semplice:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
Successivamente implementeremo il tratto (creare un'istanza della classe del tipo):
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
}
}
Al momento sarebbe molto complicato utilizzare le nostre istanze Addable:
import Instances._
val three = addableInt.add(1,2)
1.add(2)
semplicemente scrivere write 1.add(2)
. Quindi creeremo una "Operation Class" (anche chiamata "Ops Class") che si occuperà sempre di un tipo che implementa Addable
.
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
Ora possiamo usare la nostra nuova funzione add
come se fosse parte di Int
e 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
}
}
Le classi "Ops" possono essere create automaticamente da macro nella libreria simulacrum :
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}