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
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow