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
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow