Поиск…


замечания

Чтобы избежать проблем с сериализацией, особенно в распределенных средах (например, Apache Spark ), наилучшей практикой является внедрение признака Serializable для экземпляров класса типов.

Класс простого типа

Класс типа - это просто trait с одним или несколькими параметрами типа:

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

Вместо расширения класса типа для каждого поддерживаемого типа предоставляется неявный экземпляр класса type. Размещение этих реализаций в сопутствующем объекте класса типов позволяет неявное разрешение работать без какого-либо специального импорта:

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
}

Если вы хотите гарантировать, что общий параметр, переданный функции, имеет экземпляр класса типа, используйте неявные параметры:

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

Вы также можете использовать привязку к контексту :

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

Вызовите вышеуказанный метод log как и любой другой метод. Он не сможет скомпилировать, если неявная реализация Show[A] не может быть найдена для A вы передаете в 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]]

В этом примере реализуется класс Show type. Это обычный тип, используемый для преобразования произвольных экземпляров произвольных типов в String s. Несмотря на то, что у каждого объекта есть метод toString , не всегда ясно, полезен ли параметр toString . С помощью класса Show type вы можете гарантировать, что все, что передано в log имеет четко определенное преобразование в String .

Расширение типа класса

В этом примере обсуждается расширение класса ниже.

trait Show[A] {
  def show: String
}

Чтобы сделать класс, который вы контролируете (и написан на Scala), расширьте класс типа, добавьте неявный его сопутствующий объект. Давайте покажем, как мы можем получить класс Person из этого примера, чтобы расширить Show :

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

Мы можем сделать этот класс расширить Show , добавив неявное к Person объекта компаньона «s:

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

Сопутствующий объект должен находиться в том же файле, что и класс, поэтому вам нужен как class Person и object Person в том же файле.

Чтобы создать класс, который вы не контролируете или не написан в Scala, расширьте класс типа, добавьте неявный к сопутствующему объекту класса type, как показано в примере Simple Type Class .

Если вы не контролируете ни класс, ни класс типа, создайте неявное, как указано выше, и import его. Использование метода log в примере простого типа :

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

Добавление типов функций классов к типам

Внедрение Scala в классы типов довольно многословно. Один из способов уменьшить многословие - ввести так называемые «классы операций». Эти классы автоматически обертывают переменную / значение, когда они импортируются для расширения функциональности.

Чтобы проиллюстрировать это, давайте сначала создадим простой класс типа:

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

Затем мы реализуем признак (создаем экземпляр класса типа):

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
  }

}

В настоящий момент было бы очень громоздко использовать наши Добавляемые экземпляры:

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

Мы предпочли бы просто написать write 1.add(2) . Поэтому мы создадим «Операционный класс» (также называемый «Класс Ops»), который всегда будет Addable тип, который реализует Addable .

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

Теперь мы можем использовать нашу новую функцию add как если бы она была частью Int и 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 могут автоматически создаваться макросами в библиотеке 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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow