|
| 1 | +--- |
| 2 | +layout: multipage-overview |
| 3 | +title: Классы типов |
| 4 | +scala3: true |
| 5 | +partof: scala3-book |
| 6 | +overview-name: "Scala 3 — Book" |
| 7 | +type: section |
| 8 | +description: В этой главе демонстрируется создание и использование классов типов. |
| 9 | +language: ru |
| 10 | +num: 64 |
| 11 | +previous-page: ca-given-imports |
| 12 | +next-page: |
| 13 | +--- |
| 14 | + |
| 15 | +Класс типов (_type class_) — это абстрактный параметризованный тип, |
| 16 | +который позволяет добавлять новое поведение к любому закрытому типу данных без использования подтипов. |
| 17 | +Если вы пришли с Java, то можно думать о классах типов как о чем-то вроде [`java.util.Comparator[T]`][comparator]. |
| 18 | + |
| 19 | +> В статье ["Type Classes as Objects and Implicits"][typeclasses-paper] (2010 г.) обсуждаются основные идеи, |
| 20 | +> лежащие в основе классов типов в Scala. |
| 21 | +> Несмотря на то, что в статье используется более старая версия Scala, идеи актуальны и по сей день. |
| 22 | +
|
| 23 | +Этот стиль программирования полезен во многих случаях, например: |
| 24 | + |
| 25 | +- выражение того, как тип, которым вы не владеете, например, из стандартной или сторонней библиотеки, соответствует такому поведению |
| 26 | +- добавление поведения к нескольким типам без введения отношений подтипов между этими типами (например, когда один расширяет другой) |
| 27 | + |
| 28 | +Классы типов — это трейты с одним или несколькими параметрами, |
| 29 | +реализации которых предоставляются в виде экземпляров `given` в Scala 3 или `implicit` значений в Scala 2. |
| 30 | + |
| 31 | +## Пример |
| 32 | + |
| 33 | +Например, `Show` - хорошо известный класс типов в Haskell, и в следующем коде показан один из способов его реализации в Scala. |
| 34 | +Если предположить, что классы Scala не содержат метода `toString`, то можно определить класс типов `Show`, |
| 35 | +чтобы добавить это поведение к любому типу, который вы хотите преобразовать в пользовательскую строку. |
| 36 | + |
| 37 | +### Класс типов |
| 38 | + |
| 39 | +Первым шагом в создании класса типов является объявление параметризованного trait, содержащего один или несколько абстрактных методов. |
| 40 | +Поскольку `Showable` содержит только один метод с именем `show`, он записывается так: |
| 41 | + |
| 42 | +{% tabs 'definition' class=tabs-scala-version %} |
| 43 | +{% tab 'Scala 2' %} |
| 44 | + |
| 45 | +```scala |
| 46 | +// класс типов |
| 47 | +trait Showable[A] { |
| 48 | + def show(a: A): String |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +{% endtab %} |
| 53 | +{% tab 'Scala 3' %} |
| 54 | + |
| 55 | +```scala |
| 56 | +// класс типов |
| 57 | +trait Showable[A]: |
| 58 | + extension (a: A) def show: String |
| 59 | +``` |
| 60 | + |
| 61 | +{% endtab %} |
| 62 | +{% endtabs %} |
| 63 | + |
| 64 | +Обратите внимание, что этот подход близок к обычному объектно-ориентированному подходу, |
| 65 | +когда обычно trait `Show` определяется следующим образом: |
| 66 | + |
| 67 | +{% tabs 'trait' class=tabs-scala-version %} |
| 68 | +{% tab 'Scala 2' %} |
| 69 | + |
| 70 | +```scala |
| 71 | +// a trait |
| 72 | +trait Show { |
| 73 | + def show: String |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +{% endtab %} |
| 78 | +{% tab 'Scala 3' %} |
| 79 | + |
| 80 | +```scala |
| 81 | +// a trait |
| 82 | +trait Show: |
| 83 | + def show: String |
| 84 | +``` |
| 85 | + |
| 86 | +{% endtab %} |
| 87 | +{% endtabs %} |
| 88 | + |
| 89 | +Следует отметить несколько важных моментов: |
| 90 | + |
| 91 | +1. Классы типов, например, `Showable` принимают параметр типа `A`, чтобы указать, для какого типа мы предоставляем реализацию `show`; |
| 92 | + в отличие от классических трейтов, наподобие `Show`. |
| 93 | +2. Чтобы добавить функциональность `show` к определенному типу `A`, классический трейт требует наследования `A extends Show`, |
| 94 | + в то время как для классов типов нам требуется реализация `Showable[A]`. |
| 95 | +3. В Scala 3, чтобы разрешить один и тот же синтаксис вызова метода в обоих случаях `Showable`, |
| 96 | + который имитирует синтаксис `Show`, мы определяем `Showable.show` как метод расширения. |
| 97 | + |
| 98 | +### Реализация конкретных экземпляров |
| 99 | + |
| 100 | +Следующий шаг — определить, какие классы `Showable` должны работать в вашем приложении, а затем реализовать для них это поведение. |
| 101 | +Например, для реализации `Showable` следующего класса `Person`: |
| 102 | + |
| 103 | +{% tabs 'person' %} |
| 104 | +{% tab 'Scala 2 и 3' %} |
| 105 | + |
| 106 | +```scala |
| 107 | +case class Person(firstName: String, lastName: String) |
| 108 | +``` |
| 109 | + |
| 110 | +{% endtab %} |
| 111 | +{% endtabs %} |
| 112 | + |
| 113 | +необходимо определить одно _каноническое значение_ типа `Showable[Person]`, т.е. экземпляр `Showable` для типа `Person`, |
| 114 | +как показано в следующем примере кода: |
| 115 | + |
| 116 | +{% tabs 'instance' class=tabs-scala-version %} |
| 117 | +{% tab 'Scala 2' %} |
| 118 | + |
| 119 | +```scala |
| 120 | +implicit val showablePerson: Showable[Person] = new Showable[Person] { |
| 121 | + def show(p: Person): String = |
| 122 | + s"${p.firstName} ${p.lastName}" |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +{% endtab %} |
| 127 | +{% tab 'Scala 3' %} |
| 128 | + |
| 129 | +```scala |
| 130 | +given Showable[Person] with |
| 131 | + extension (p: Person) def show: String = |
| 132 | + s"${p.firstName} ${p.lastName}" |
| 133 | +``` |
| 134 | + |
| 135 | +{% endtab %} |
| 136 | +{% endtabs %} |
| 137 | + |
| 138 | +### Использование класса типов |
| 139 | + |
| 140 | +Теперь вы можете использовать этот класс типов следующим образом: |
| 141 | + |
| 142 | +{% tabs 'usage' class=tabs-scala-version %} |
| 143 | +{% tab 'Scala 2' %} |
| 144 | + |
| 145 | +```scala |
| 146 | +val person = Person("John", "Doe") |
| 147 | +println(showablePerson.show(person)) |
| 148 | +``` |
| 149 | + |
| 150 | +Обратите внимание, что на практике классы типов обычно используются со значениями, тип которых неизвестен, |
| 151 | +в отличие от type `Person`, как показано в следующем разделе. |
| 152 | + |
| 153 | +{% endtab %} |
| 154 | +{% tab 'Scala 3' %} |
| 155 | + |
| 156 | +```scala |
| 157 | +val person = Person("John", "Doe") |
| 158 | +println(person.show) |
| 159 | +``` |
| 160 | + |
| 161 | +{% endtab %} |
| 162 | +{% endtabs %} |
| 163 | + |
| 164 | +Опять же, если бы в Scala не было метода `toString`, доступного для каждого класса, вы могли бы использовать эту технику, |
| 165 | +чтобы добавить поведение `Showable` к любому классу, который вы хотите преобразовать в `String`. |
| 166 | + |
| 167 | +### Написание методов, использующих класс типов |
| 168 | + |
| 169 | +Как и в случае с наследованием, вы можете определить методы, которые используют `Showable` в качестве параметра типа: |
| 170 | + |
| 171 | +{% tabs 'method' class=tabs-scala-version %} |
| 172 | +{% tab 'Scala 2' %} |
| 173 | + |
| 174 | +```scala |
| 175 | +def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit = |
| 176 | + as.foreach(a => println(showable.show(a))) |
| 177 | + |
| 178 | +showAll(List(Person("Jane"), Person("Mary"))) |
| 179 | +``` |
| 180 | + |
| 181 | +{% endtab %} |
| 182 | +{% tab 'Scala 3' %} |
| 183 | + |
| 184 | +```scala |
| 185 | +def showAll[A: Showable](as: List[A]): Unit = |
| 186 | + as.foreach(a => println(a.show)) |
| 187 | + |
| 188 | +showAll(List(Person("Jane"), Person("Mary"))) |
| 189 | +``` |
| 190 | + |
| 191 | +{% endtab %} |
| 192 | +{% endtabs %} |
| 193 | + |
| 194 | +### Класс типов с несколькими методами |
| 195 | + |
| 196 | +Обратите внимание: если вы хотите создать класс типов с несколькими методами, исходный синтаксис выглядит следующим образом: |
| 197 | + |
| 198 | +{% tabs 'multiple-methods' class=tabs-scala-version %} |
| 199 | +{% tab 'Scala 2' %} |
| 200 | + |
| 201 | +```scala |
| 202 | +trait HasLegs[A] { |
| 203 | + def walk(a: A): Unit |
| 204 | + def run(a: A): Unit |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +{% endtab %} |
| 209 | +{% tab 'Scala 3' %} |
| 210 | + |
| 211 | +```scala |
| 212 | +trait HasLegs[A]: |
| 213 | + extension (a: A) |
| 214 | + def walk(): Unit |
| 215 | + def run(): Unit |
| 216 | +``` |
| 217 | + |
| 218 | +{% endtab %} |
| 219 | +{% endtabs %} |
| 220 | + |
| 221 | +### Пример из реального мира |
| 222 | + |
| 223 | +В качестве примера из реального мира, как классы типов используются в Scala 3, |
| 224 | +см. обсуждение `CanEqual` в [разделе Multiversal Equality][multiversal]. |
| 225 | + |
| 226 | +[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf |
| 227 | + |
| 228 | +[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %} |
| 229 | +[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html |
| 230 | +[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %} |
0 commit comments