Scala Language
Klasy typów
Szukaj…
Uwagi
Aby uniknąć problemów z serializacją, szczególnie w środowiskach rozproszonych (np. Apache Spark ), najlepszą praktyką jest wdrożenie cechy Serializable
dla instancji klasy typu.
Prosta klasa typów
Klasa typu to po prostu trait
z jednym lub więcej parametrami typu:
trait Show[A] {
def show(a: A): String
}
Zamiast rozszerzania klasy typu, dla każdego obsługiwanego typu udostępniana jest niejawna instancja klasy typu. Umieszczenie tych implementacji w obiekcie towarzyszącym klasy typu pozwala na niejawne rozwiązanie problemu bez specjalnego importu:
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
}
Jeśli chcesz zagwarantować, że parametr ogólny przekazany do funkcji ma instancję klasy typu, użyj parametrów niejawnych:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
Możesz także użyć powiązania kontekstu :
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Wywołaj powyższą metodę log
, jak każdą inną metodę. Kompilacja nie powiedzie się, jeśli nie można znaleźć niejawnej implementacji Show[A]
dla A
przekazywanego do 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]]
Ten przykład implementuje klasę typu Show
. Jest to typowa klasa typów używana do konwertowania dowolnych instancji dowolnych typów na String
. Mimo że każdy obiekt ma metodę toString
, nie zawsze jest jasne, czy toString
jest zdefiniowane w przydatny sposób. Za pomocą klasy typu Show
możesz zagwarantować, że wszystko, co przekazane do log
ma dobrze zdefiniowaną konwersję na String
.
Rozszerzanie klasy typu
W tym przykładzie omówiono rozszerzenie poniższej klasy typów.
trait Show[A] {
def show: String
}
Aby uczynić klasę, którą kontrolujesz (i jest napisana w Scali), rozszerzyć klasę typu, dodaj niejawne do jej obiektu towarzyszącego. Pokażmy, w jaki sposób możemy uzyskać klasę Person
z tego przykładu, aby rozszerzyć Show
:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
Możemy sprawić, aby ta klasa rozszerzyła Show
poprzez dodanie niejawnego obiektu towarzyszącego Person
:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
Obiekt towarzyszący musi znajdować się w tym samym pliku co klasa, dlatego potrzebujesz zarówno class Person
i object Person
w tym samym pliku.
Aby utworzyć klasę, której nie kontrolujesz lub nie jest napisana w Scali, rozszerz klasę typu, dodaj niejawny obiekt towarzyszący klasy typu, jak pokazano w przykładzie z prostą klasą typów .
Jeśli nie kontrolujesz ani klasy, ani klasy typu, utwórz niejawne jak wyżej gdziekolwiek i import
je. Używając metody log
na przykładzie prostej klasy typów :
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) }
}
Dodaj funkcje klas typów do typów
Implementacja klas typów przez Scalę jest raczej pełna. Jednym ze sposobów ograniczenia gadatliwości jest wprowadzenie tak zwanych „klas operacji”. Klasy te automatycznie zawijają zmienną / wartość, gdy są importowane w celu rozszerzenia funkcjonalności.
Aby to zilustrować, stwórzmy najpierw prostą klasę typów:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
Następnie zaimplementujemy cechę (wystąpienie klasy typu):
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
}
}
W tej chwili korzystanie z naszych instancji Addable byłoby bardzo kłopotliwe:
import Instances._
val three = addableInt.add(1,2)
Wolelibyśmy napisać napisz 1.add(2)
. Dlatego stworzymy „klasę operacji” (zwaną również „klasą operacji”), która zawsze będzie się Addable
w typ implementujący Addable
.
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
Teraz możemy użyć naszej nowej funkcji add
tak jakby była częścią Int
i 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
}
}
Klasy „Ops” mogą być tworzone automatycznie przez makra w bibliotece simulacrum :
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}