Статьи

Отрывки Scala 4: Прокачайте мой шаблон библиотеки с помощью классов типов.

Я хотел написать статью о забавных частях scalaz, но подумал, что лучше было бы сначала немного поближе познакомиться с системой классов типов, предоставляемой scala. Итак, в этом фрагменте мы рассмотрим небольшую часть работы классов типов и можем помочь вам в написании более общего кода.

Больше фрагментов можно найти здесь:

  1. Отрывки Scala 1: складные
  2. Фрагменты Scala 2: список символов магии
  3. Отрывки Scala 3: списки вместе с картой, плоской картой, сжатием и уменьшением
  4. Фрагменты Scala 4: Прокачайте мой шаблон библиотеки с помощью классов типов

Типы классов

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

«В информатике класс типов — это конструкция системы типов, которая поддерживает специальный полиморфизм. Это достигается путем добавления ограничений на переменные типа в параметрически полиморфных типах. Такое ограничение обычно включает в себя класс типа «T» и переменную типа «a» и означает, что «a» может быть создан только для типа, члены которого поддерживают перегруженные операции, связанные с «T». »

По сути, классы типов позволяют добавлять функциональность к существующим классам, не затрагивая существующие классы. Например, мы могли бы добавить стандартную «сопоставимую» функциональность в строки без необходимости изменять существующие классы. Обратите внимание, что вы также можете просто использовать неявные функции для добавления пользовательского поведения (например, «шаблон Pimp my library», https://coderwall.com/p/k_1jzw/scala-s-pimp-my-library-pattern-example ), но использование классов типов намного безопаснее и гибче. Хорошее обсуждение этого можно найти здесь ( http://stackoverflow.com/questions/8524878/implicit-conversion-vs-type-c… ).

Итак, достаточно введения, давайте рассмотрим очень простой пример классов типов. Создание класса типов в Scala выполняется в несколько разных этапов.

Первый шаг — создать черту. Эта черта является фактическим типом класса и определяет функциональность, которую мы хотим предоставить. Для этой статьи мы создадим очень надуманный пример, в котором мы определим черту «Duplicate». С этой чертой мы дублируем определенный объект. Поэтому, когда мы получаем строковое значение «hello», мы хотим вернуть «hellohello», когда мы получаем целое число, мы возвращаем значение * значение, когда мы получаем символ «c», мы возвращаем «cc». Все это в безопасном виде.

Наш класс типов на самом деле очень прост:

1
2
3
trait Duplicate[A,B] {
    def duplicate(value: A): B
  }

Обратите внимание, что это очень похоже на черты смешивания в Scala, но используется совершенно иначе. Как только мы получим определение класса типов, следующим шагом будет создание некоторых реализаций по умолчанию. Мы делаем это в объекте-компаньоне черты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
object Duplicate {
  
    // implemented as a singleton object
    implicit object DuplicateString extends Duplicate[String,String] {
      def duplicate(value: String) = value.concat(value)
    }
  
    // or directly, which I like better.
    implicit val duplicateInt = new Duplicate[Int, Int] {
      def duplicate(value: Int) = value * value
    }
  
    implicit val duplicateChar = new Duplicate[Char, String] {
      def duplicate(value: Char) = value.toString + value.toString
    }
  }
 }

Как видите, мы можем сделать это несколькими разными способами. Самая важная часть здесь — неявное ключевое слово. Используя это ключевое слово, мы можем сделать эти элементы доступными при определенных обстоятельствах. Когда вы посмотрите на реализацию, вы заметите, что все они очень прямолинейны. Мы просто реализуем черту, которую мы определили для определенных типов. В этом случае для строки, целого числа и символа.

Теперь мы можем начать использовать классы типов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
object DuplicateWriter {
  
  // import the conversions for use within this object
  import conversions.Duplicate
  
  // Generic method that takes a value, and looks for an implicit
  // conversion of type Duplicate. If no implicit Duplicate is available
  // an error will be thrown. Scala will first look in the local
  // scope before looking for implicits in the companion object
  // of the trait class.
  def write[A,B](value: A)(implicit dup: Duplicate[A, B]) : B = {
    dup.duplicate(value)
  }
}
  
  
// simple app that runs our conversions
object Example extends App {
  import snippets.conversions.Duplicate
  
  implicit val anotherDuplicateInt = new Duplicate[Int, Int] {
    def duplicate(value: Int) = value + value
  }
  
  println(DuplicateWriter.write("Hello"))
  println(DuplicateWriter.write('c'))
  println(DuplicateWriter.write(0))
  println(DuplicateWriter.write(0)(Duplicate.duplicateInt))
  
}

В этом примере мы создали DuplicateWriter, который вызывает дублирующую функцию в классе предоставления, ища соответствующую реализацию вызовов типов. В нашем примере объекта мы также переопределяем дублирующую функцию по умолчанию для типа Int с пользовательской. В последней строке мы предоставляем конкретный объект Duplicate для использования DuplicateWriter. Результат этого приложения:

1
2
3
4
20
100
HelloHello
cc

Если мы запускаем с неподдерживаемым типом (например, с двойным):

1
println(DuplicateWriter.write(0d))

Мы получаем следующие сообщения времени компиляции (в данном случае intellij IDE).

1
2
3
4
5
6
7
8
Error:(56, 32) could not find implicit value for parameter dup: snippets.conversions.Duplicate[Double,B]
  println(DuplicateWriter.write(0d))
                               ^
  
Error:(56, 32) not enough arguments for method write: (implicit dup: snippets.conversions.Duplicate[Double,B])B.
Unspecified value parameter dup.
  println(DuplicateWriter.write(0d))
                               ^

Мы также можем настроить первое из этих сообщений, добавив следующую аннотацию к нашему определению класса / типа:

1
2
3
4
@implicitNotFound("No member of type class Duplicate in scope for ${T}")
  trait Duplicate[A,B] {
    def duplicate(value: A): B
  }

Так что это очень быстрое введение в классы типов. Как видите, они предоставляют очень простой способ добавления пользовательских функций в классы, даже если вы ими не управляете. В следующем фрагменте мы рассмотрим пару общих, очень полезных классов типов из библиотеки Scalaz.