Suche…


Bemerkungen

Um Serialisierungsprobleme zu vermeiden, insbesondere in verteilten Umgebungen (z. B. Apache Spark ), empfiehlt es sich, die Serializable Eigenschaft für Typklasseninstanzen zu implementieren.

Einfache Typenklasse

Eine Typenklasse ist einfach eine trait mit einem oder mehreren Typparametern:

trait Show[A] {
  def show(a: A): String
}

Anstatt eine Typenklasse zu erweitern, wird für jeden unterstützten Typ eine implizite Instanz der Typklasse bereitgestellt. Durch das Platzieren dieser Implementierungen im Companion-Objekt der Typklasse kann die implizite Auflösung ohne spezielle Importe funktionieren:

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
}

Wenn Sie sicherstellen möchten, dass ein generischer Parameter, der an eine Funktion übergeben wird, über eine Instanz einer Typklasse verfügt, verwenden Sie implizite Parameter:

def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
  println(showInstance.show(a))
}

Sie können auch eine Kontextbindung verwenden :

def log[A: Show](a: A): Unit = {
  println(implicitly[Show[A]].show(a))
}

Rufen Sie die obige log wie jede andere Methode auf. Es kann nicht kompiliert werden, wenn für das A Sie an das log keine implizite Show[A] -Implementierung gefunden werden kann

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 diesem Beispiel wird die Klasse " Show implementiert. Dies ist eine gebräuchliche Typenklasse, mit der beliebige Instanzen beliebiger Typen in String s konvertiert werden. Obwohl jedes Objekt über eine toString Methode verfügt, ist nicht immer klar, ob toString sinnvoll definiert ist oder nicht. Bei der Verwendung der Show Typklasse, können Sie garantieren , dass alles übergeben log eine gut definierte Umwandlung hat String .

Typenklasse erweitern

In diesem Beispiel wird die Erweiterung der folgenden Typklasse erläutert.

trait Show[A] {
  def show: String
}

Um eine Klasse, die Sie steuern (und in Scala geschrieben) haben, um die Typklasse zu erweitern, fügen Sie ihrem Begleitobjekt eine implizite Klasse hinzu. Lassen Sie uns zeigen , wie wir die bekommen können Person Klasse von diesem Beispiel erweitern Show :

class Person(val fullName: String) {    
  def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}

Wir können diese Klasse machen verlängern Show durch eine implizite zum Hinzufügen Person ‚s Begleitobjekt:

object Person {
  implicit val personShow: Show[Person] = new Show {
    def show(p: Person): String = s"Person(${p.fullname})"
  }
}

Ein Begleitobjekt muss sich in derselben Datei wie die Klasse befinden. Sie benötigen also sowohl die class Person als auch das object Person in derselben Datei.

Um eine Klasse zu erstellen, die Sie nicht kontrollieren oder in Scala nicht geschrieben sind, erweitern Sie die Typklasse, fügen Sie dem Begleitobjekt der Typklasse eine implizite Funktion hinzu, wie im Beispiel der Simple Type Class gezeigt .

Wenn Sie weder die Klasse noch die Typenklasse steuern, erstellen Sie ein implizites Objekt wie oben und import es. Verwenden der log im Beispiel für die einfache Typklasse :

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) }
}

Fügen Sie Typklassenfunktionen zu Typen hinzu

Scalas Implementierung von Typklassen ist ziemlich ausführlich. Eine Möglichkeit, die Ausführlichkeit zu reduzieren, ist die Einführung sogenannter "Operation Classes". Diese Klassen wickeln automatisch eine Variable / einen Wert ein, wenn sie zur Erweiterung der Funktionalität importiert werden.

Um dies zu veranschaulichen, erstellen wir zunächst eine einfache Typenklasse:

// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
  def add(x: A, y: A): A
}

Als Nächstes implementieren wir die Eigenschaft (Instanziierung der Typenklasse):

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
  }

}

Im Moment wäre es sehr umständlich, unsere Addable-Instanzen zu verwenden:

import Instances._
val three = addableInt.add(1,2)

Wir schreiben 1.add(2) . Deshalb erstellen wir eine "Operationsklasse" (auch als "Ops-Klasse" bezeichnet), die immer einen Typ Addable , der Addable implementiert.

object Ops {
  implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
    def add(other: A): A = A.add(self, other)
  }
}

Jetzt können wir unsere neue Funktion add als ob sie Teil von Int und 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 können automatisch von Makros in der Simulacrum- Bibliothek erstellt werden:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow