Zoeken…


Opmerkingen

Om problemen met serialisatie te voorkomen, met name in gedistribueerde omgevingen (bijvoorbeeld Apache Spark ), is het een goede gewoonte om de eigenschap Serializable te implementeren voor instanties van type klasse.

Eenvoudige typeklasse

Een typeklasse is gewoon een trait met een of meer typeparameters:

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

In plaats van een typeklasse uit te breiden, wordt voor elk ondersteund type een impliciete instantie van de typeklasse verstrekt. Door deze implementaties in het bijbehorende object van de type-klasse te plaatsen, kan impliciete oplossing werken zonder speciale invoer:

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
}

Als u wilt garanderen dat een generieke parameter die aan een functie wordt doorgegeven, een instantie van een typeklasse heeft, gebruikt u impliciete parameters:

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

U kunt ook een contextgebonden context gebruiken :

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

Roep de bovenstaande log zoals elke andere methode. Het kan niet worden gecompileerd als er geen impliciete Show[A] -implementatie kan worden gevonden voor de A u wilt 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]]

In dit voorbeeld wordt de klasse Show type geïmplementeerd. Dit is een algemene typeklasse die wordt gebruikt om willekeurige instanties van willekeurige typen om te zetten in String s. Hoewel elk object een toString methode heeft, is het niet altijd duidelijk of toString op een nuttige manier is gedefinieerd. Met behulp van de klasse Show type kunt u garanderen dat alles wat wordt doorgegeven aan het log een goed gedefinieerde conversie naar String .

Een typeklasse uitbreiden

In dit voorbeeld wordt de uitbreiding van de onderstaande typeklasse besproken.

trait Show[A] {
  def show: String
}

Als u een klasse wilt maken die u bestuurt (en in Scala is geschreven), breidt u de typeklasse uit, voegt u een impliciet toe aan het bijbehorende object. Laten we laten zien hoe we de Person klasse uit dit voorbeeld kunnen krijgen om Show uit te breiden:

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

We kunnen deze klasse uit te breiden Show door het toevoegen van een impliciet naar Person metgezel object 's:

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

Een bijbehorend object moet zich in hetzelfde bestand bevinden als de klasse, dus u moet zowel de class Person als de object Person in hetzelfde bestand hebben.

Als u een klasse wilt maken die u niet beheert of niet in Scala is geschreven, breidt u de type-klasse uit, voegt u een impliciet toe aan het bijbehorende object van de type-klasse, zoals weergegeven in het voorbeeld van de Simple Type Class .

Als u noch de klasse, noch de typeklasse beheert, maakt u een impliciet zoals hierboven en import het. Met behulp van de log in het voorbeeld van 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) }
}

Voeg typeklassefuncties toe aan typen

Scala's implementatie van typeklassen is vrij uitgebreid. Een manier om de verbosity te verminderen is door zogenaamde "Operation Classes" te introduceren. Deze klassen verpakken automatisch een variabele / waarde wanneer ze worden geïmporteerd om de functionaliteit uit te breiden.

Om dit te illustreren, laten we eerst een eenvoudige typeklasse maken:

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

Vervolgens zullen we de eigenschap implementeren (de typeklasse instantiëren):

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
  }

}

Op dit moment zou het erg omslachtig zijn om onze Addable-instanties te gebruiken:

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

We schrijven liever schrijven 1.add(2) . Daarom maken we een "Operation Class" (ook wel een "Ops Class" genoemd) die altijd een type Addable dat Addable implementeert.

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

Nu kunnen we onze nieuwe functie add alsof het onderdeel was van Int en 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 kunnen automatisch door macro's in de simulacrumbibliotheek worden gemaakt:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow