Buscar..


Observaciones

Para evitar problemas de serialización, particularmente en entornos distribuidos (por ejemplo, Apache Spark ), es una buena práctica implementar el rasgo Serializable para instancias de clase de tipo.

Tipo de clase simple

Una clase de tipo es simplemente un trait con uno o más parámetros de tipo:

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

En lugar de ampliar una clase de tipo, se proporciona una instancia implícita de la clase de tipo para cada tipo admitido. La colocación de estas implementaciones en el objeto complementario de la clase de tipo permite que la resolución implícita funcione sin ninguna importación especial:

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
}

Si desea garantizar que un parámetro genérico pasado a una función tiene una instancia de una clase de tipo, use parámetros implícitos:

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

También puedes usar un enlace de contexto :

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

Llame al método de log anterior como cualquier otro método. No se compilará si no se puede encontrar Show[A] implementación implícita de Show[A] para la A que se pasa 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]]

Este ejemplo implementa la clase de tipo Show . Esta es una clase de tipo común utilizada para convertir instancias arbitrarias de tipos arbitrarios en String . Aunque cada objeto tiene un método toString , no siempre está claro si toString se define o no de una manera útil. Con el uso de la clase de tipo Show , puede garantizar que todo lo que se pase al log tenga una conversión bien definida a String .

Extendiendo una Clase Tipo

Este ejemplo discute la extensión de la siguiente clase de tipos.

trait Show[A] {
  def show: String
}

Para hacer que una clase que usted controla (y está escrita en Scala) extienda la clase de tipo, agregue un implícito a su objeto complementario. Vamos a mostrar cómo podemos obtener la clase Person de este ejemplo para ampliar Show :

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

Podemos hacer que esta clase amplíe Show agregando un implícito al objeto complementario de Person :

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

Un objeto complementario debe estar en el mismo archivo que la clase, por lo que necesita la class Person y el object Person en el mismo archivo.

Para hacer una clase que no controla, o no está escrita en Scala, extienda la clase de tipo, agregue un implícito al objeto compañero de la clase de tipo, como se muestra en el ejemplo de Clase de tipo simple .

Si no controla ni la clase ni la clase de tipo, cree un implícito como el de arriba en cualquier lugar e import . Usando el método de log en el ejemplo de clase de tipo simple :

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

Añadir funciones de clase de tipo a los tipos

La implementación de Scala de las clases de tipos es bastante detallada. Una forma de reducir la verbosidad es introducir las llamadas "clases de operación". Estas clases envolverán automáticamente una variable / valor cuando se importen para ampliar la funcionalidad.

Para ilustrar esto, primero creamos una clase de tipo simple:

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

A continuación vamos a implementar el rasgo (instanciar la clase de 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
  }

}

En este momento sería muy incómodo utilizar nuestras instancias de Addable:

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

Preferiríamos simplemente escribir escribir 1.add(2) . Por lo tanto, crearemos una "Clase de operación" (también llamada "Clase de Operaciones") que siempre se ajustará a un tipo que implementa Addable .

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

Ahora podemos usar nuestra nueva función add como si fuera parte de Int y 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

  }
}

Las clases de "operaciones" se pueden crear automáticamente mediante macros en la biblioteca de simulacros :

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow