diff --git a/_overviews/scala3-book/types-adts-gadts.md b/_overviews/scala3-book/types-adts-gadts.md index a4f31e0040..204519c030 100644 --- a/_overviews/scala3-book/types-adts-gadts.md +++ b/_overviews/scala3-book/types-adts-gadts.md @@ -2,7 +2,7 @@ title: Algebraic Data Types type: section description: This section introduces and demonstrates algebraic data types (ADTs) in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 53 previous-page: types-union next-page: types-variance diff --git a/_overviews/scala3-book/types-variance.md b/_overviews/scala3-book/types-variance.md index 7aae5b5fac..deb2492e80 100644 --- a/_overviews/scala3-book/types-variance.md +++ b/_overviews/scala3-book/types-variance.md @@ -2,7 +2,7 @@ title: Variance type: section description: This section introduces and demonstrates variance in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 54 previous-page: types-adts-gadts next-page: types-opaque-types diff --git a/_ru/scala3/book/types-adts-gadts.md b/_ru/scala3/book/types-adts-gadts.md new file mode 100644 index 0000000000..199d7f1f33 --- /dev/null +++ b/_ru/scala3/book/types-adts-gadts.md @@ -0,0 +1,222 @@ +--- +layout: multipage-overview +title: Алгебраические типы данных +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены и демонстрируются алгебраические типы данных (ADT) в Scala 3. +language: ru +num: 53 +previous-page: types-union +next-page: types-variance +--- + +Только в Scala 3 + +Алгебраические типы данных (ADT) могут быть созданы с помощью конструкции `enum`, +поэтому кратко рассмотрим перечисления, прежде чем рассматривать ADT. + +## Перечисления + +_Перечисление_ используется для определения типа, состоящего из набора именованных значений: + +```scala +enum Color: + case Red, Green, Blue +``` + +который можно рассматривать как сокращение для: + +```scala +enum Color: + case Red extends Color + case Green extends Color + case Blue extends Color +``` + +#### Параметры + +Перечисления могут быть параметризованы: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +``` + +Таким образом, каждый из различных вариантов содержит параметр `rgb`, +которому присваивается соответствующее значение: + +```scala +println(Color.Green.rgb) // выводит 65280 +``` + +#### Пользовательские определения + +Перечисления также могут содержать пользовательские определения: + +```scala +enum Planet(mass: Double, radius: Double): + + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + // остальные 5 или 6 планет ... +``` + +Подобно классам и `case` классам, вы также можете определить сопутствующий объект для перечисления: + +```scala +object Planet: + def main(args: Array[String]) = + val earthWeight = args(0).toDouble + val mass = earthWeight / Earth.surfaceGravity + for (p <- values) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") +``` + +## Алгебраические типы данных (ADTs) + +Концепция `enum` является достаточно общей, +чтобы также поддерживать _алгебраические типы данных_ (ADT) и их обобщенную версию (GADT). +Вот пример, показывающий, как тип `Option` может быть представлен в виде АТД: + +```scala +enum Option[+T]: + case Some(x: T) + case None +``` + +В этом примере создается перечисление `Option` с параметром ковариантного типа `T`, +состоящим из двух вариантов `Some` и `None`. +`Some` _параметризуется_ значением параметра `x`; +это сокращение для написания `case` класса, расширяющего `Option`. +Поскольку `None` не параметризуется, то он считается обычным enum значением. + +Предложения `extends`, которые были опущены в предыдущем примере, также могут быть указаны явно: + +```scala +enum Option[+T]: + case Some(x: T) extends Option[T] + case None extends Option[Nothing] +``` + +Как и в случае с обычным `enum` значениями, варианты enum определяются в его сопутствующем объекте, +поэтому они называются `Option.Some` и `Option.None` (если только определения не «вытягиваются» при импорте): + +```scala +scala> Option.Some("hello") +val res1: t2.Option[String] = Some(hello) + +scala> Option.None +val res2: t2.Option[Nothing] = None +``` + +Как и в других случаях использования перечисления, АТД могут определять дополнительные методы. +Например, вот снова `Option`, с методом `isDefined` и конструктором `Option(...)` в сопутствующем объекте: + +```scala +enum Option[+T]: + case Some(x: T) + case None + + def isDefined: Boolean = this match + case None => false + case Some(_) => true + +object Option: + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) +``` + +Перечисления и АТД используют одну и ту же синтаксическую конструкцию, +поэтому их можно рассматривать просто как два конца спектра, и вполне допустимо создавать гибриды. +Например, приведенный ниже код реализует `Color` либо с тремя значениями перечисления, +либо с параметризованным вариантом, принимающим значение RGB: + +```scala +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +``` + +#### Рекурсивные перечисления + +До сих пор все перечисления, которые мы определяли, состояли из различных вариантов значений или case классов. +Перечисления также могут быть рекурсивными, как показано в приведенном ниже примере кодирования натуральных чисел: + +```scala +enum Nat: + case Zero + case Succ(n: Nat) +``` + +Например, значение `Succ(Succ(Zero))` представляет число `2` в унарной кодировке. +Списки могут быть определены похожим образом: + +```scala +enum List[+A]: + case Nil + case Cons(head: A, tail: List[A]) +``` + +## Обобщенные алгебраические типы данных (GADT) + +Приведенная выше нотация для перечислений очень краткая +и служит идеальной отправной точкой для моделирования ваших типов данных. +Поскольку мы всегда можем быть более подробными, то можем выразить гораздо более мощные типы: +обобщенные алгебраические типы данных (GADT). + +Вот пример GADT, в котором параметр типа (`T`) указывает на тип содержимого, хранящегося в `Box`: + +```scala +enum Box[T](contents: T): + case IntBox(n: Int) extends Box[Int](n) + case BoolBox(b: Boolean) extends Box[Boolean](b) +``` + +Сопоставление с образцом с конкретным конструктором (`IntBox` или `BoolBox`) восстанавливает информацию о типе: + +```scala +def extract[T](b: Box[T]): T = b match + case IntBox(n) => n + 1 + case BoolBox(b) => !b +``` + +Безопасно возвращать `Int` в первом случае, так как мы знаем из сопоставления с образцом, что ввод был `IntBox`. + +## Дешугаризация перечислений + +_Концептуально_ перечисления можно рассматривать как определение запечатанного класса вместе с сопутствующим ему объектом. +Давайте посмотрим на дешугаризацию нашего перечисления `Color`: + +```scala +sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum +object Color: + case object Red extends Color(0xFF0000) { def ordinal = 0 } + case object Green extends Color(0x00FF00) { def ordinal = 1 } + case object Blue extends Color(0x0000FF) { def ordinal = 2 } + case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 } + + def fromOrdinal(ordinal: Int): Color = ordinal match + case 0 => Red + case 1 => Green + case 2 => Blue + case _ => throw new NoSuchElementException(ordinal.toString) +``` + +Заметьте, что вышеописанная дешугаризация упрощена, и мы намеренно опускаем [некоторые детали][desugar-enums]. + +В то время как перечисления можно кодировать вручную с помощью других конструкций, +использование перечислений является более кратким, +а также включает несколько дополнительных утилит (таких, как метод `fromOrdinal`). + +[desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html diff --git a/_ru/scala3/book/types-union.md b/_ru/scala3/book/types-union.md index 6c28497626..6185d59e12 100644 --- a/_ru/scala3/book/types-union.md +++ b/_ru/scala3/book/types-union.md @@ -9,7 +9,7 @@ description: В этом разделе представлены объедин language: ru num: 52 previous-page: types-intersection -next-page: +next-page: types-adts-gadts --- Только в Scala 3 diff --git a/_ru/scala3/book/types-variance.md b/_ru/scala3/book/types-variance.md new file mode 100644 index 0000000000..d4a4eaa7b7 --- /dev/null +++ b/_ru/scala3/book/types-variance.md @@ -0,0 +1,283 @@ +--- +layout: multipage-overview +title: Вариантность +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлена и демонстрируется вариантность в Scala 3. +language: ru +num: 54 +previous-page: types-adts-gadts +next-page: +--- + +_Вариантность_ (_variance_) параметра типа управляет подтипом параметризованных типов (таких, как классы или трейты). + +Чтобы объяснить вариантность, давайте рассмотрим следующие определения типов: + +{% tabs types-variance-1 %} +{% tab 'Scala 2 и 3' %} + +```scala +trait Item { def productNumber: String } +trait Buyable extends Item { def price: Int } +trait Book extends Buyable { def isbn: String } + +``` + +{% endtab %} +{% endtabs %} + +Предположим также следующие параметризованные типы: + +{% tabs types-variance-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-2 %} + +```scala +// пример инвариантного типа +trait Pipeline[T] { + def process(t: T): T +} + +// пример ковариантного типа +trait Producer[+T] { + def make: T +} + +// пример контрвариантного типа +trait Consumer[-T] { + def take(t: T): Unit +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-2 %} + +```scala +// пример инвариантного типа +trait Pipeline[T]: + def process(t: T): T + +// пример ковариантного типа +trait Producer[+T]: + def make: T + +// пример контрвариантного типа +trait Consumer[-T]: + def take(t: T): Unit +``` + +{% endtab %} +{% endtabs %} + +В целом существует три режима вариантности (variance): + +- **инвариант** (invariant) — значение по умолчанию, написанное как `Pipeline[T]` +- **ковариантный** (covariant) — помечен знаком `+`, например `Producer[+T]` +- **контравариантный** (contravariant) — помечен знаком `-`, как в `Consumer[-T]` + +Подробнее рассмотрим, что означает и как используется эта аннотация. + +### Инвариантные типы + +По умолчанию такие типы, как `Pipeline`, инвариантны в своем аргументе типа (в данном случае `T`). +Это означает, что такие типы, как `Pipeline[Item]`, `Pipeline[Buyable]` и `Pipeline[Book]`, _не являются подтипами_ друг друга. + +И это правильно! Предположим, что следующий метод использует два значения (`b1`, `b2`) типа `Pipeline[Buyable]` +и передает свой аргумент `b` методу `process` при его вызове на `b1` и `b2`: + +{% tabs types-variance-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-3 %} + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = { + val b1 = p1.process(b) + val b2 = p2.process(b) + if (b1.price < b2.price) b1 else b2 + } +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-3 %} + +```scala +def oneOf( + p1: Pipeline[Buyable], + p2: Pipeline[Buyable], + b: Buyable +): Buyable = + val b1 = p1.process(b) + val b2 = p2.process(b) + if b1.price < b2.price then b1 else b2 +``` + +{% endtab %} +{% endtabs %} + +Теперь вспомните, что у нас есть следующие _отношения подтипов_ между нашими типами: + +{% tabs types-variance-4 %} +{% tab 'Scala 2 и 3' %} + +```scala +Book <: Buyable <: Item +``` + +{% endtab %} +{% endtabs %} + +Мы не можем передать `Pipeline[Book]` методу `oneOf`, +потому что в реализации `oneOf` мы вызываем `p1` и `p2` со значением типа `Buyable`. +`Pipeline[Book]` ожидает `Book`, что потенциально может вызвать ошибку времени выполнения. + +Мы не можем передать `Pipeline[Item]`, потому что вызов `process` обещает вернуть `Item`; +однако мы должны вернуть `Buyable`. + +#### Почему Инвариант? + +На самом деле тип `Pipeline` должен быть инвариантным, +так как он использует свой параметр типа `T` _и в качестве_ аргумента, _и в качестве_ типа возвращаемого значения. +По той же причине некоторые типы в библиотеке коллекций Scala, такие как `Array` или `Set`, также являются _инвариантными_. + +### Ковариантные типы + +В отличие от `Pipeline`, который является инвариантным, +тип `Producer` помечается как **ковариантный** (covariant) путем добавления к параметру типа префикса `+`. +Это допустимо, так как параметр типа используется только в качестве типа _возвращаемого_ значения. + +Пометка типа как ковариантного означает, что мы можем передать (или вернуть) `Producer[Book]` там, +где ожидается `Producer[Buyable]`. И на самом деле, это разумно. +Тип `Producer[Buyable].make` только обещает _вернуть_ `Buyable`. +Но для пользователей `make`, так же допустимо принять `Book`, который является подтипом `Buyable`, +то есть это _по крайней мере_ `Buyable`. + +Это иллюстрируется следующим примером, где функция `makeTwo` ожидает `Producer[Buyable]`: + +{% tabs types-variance-5 %} +{% tab 'Scala 2 и 3' %} + +```scala +def makeTwo(p: Producer[Buyable]): Int = + p.make.price + p.make.price +``` + +{% endtab %} +{% endtabs %} + +Допустимо передать в `makeTwo` производителя книг: + +{% tabs types-variance-6 %} +{% tab 'Scala 2 и 3' %} + +```scala +val bookProducer: Producer[Book] = ??? +makeTwo(bookProducer) +``` + +{% endtab %} +{% endtabs %} + +Вызов `price` в рамках `makeTwo` по-прежнему действителен и для `Book`. + +#### Ковариантные типы для неизменяемых контейнеров + +Ковариантность чаще всего встречается при работе с неизменяемыми контейнерами, такими как `List`, `Seq`, `Vector` и т.д. + +Например, `List` и `Vector` определяются приблизительно так: + +{% tabs types-variance-7 %} +{% tab 'Scala 2 и 3' %} + +```scala +class List[+A] ... +class Vector[+A] ... +``` + +{% endtab %} +{% endtabs %} + +Таким образом, можно использовать `List[Book]` там, где ожидается `List[Buyable]`. +Это также интуитивно имеет смысл: если ожидается коллекция вещей, которые можно купить, +то вполне допустимо получить коллекцию книг. +В примере выше у книг есть дополнительный метод `isbn`, но дополнительные возможности можно игнорировать. + +### Контравариантные типы + +В отличие от типа `Producer`, который помечен как ковариантный, +тип `Consumer` помечен как **контравариантный** (contravariant) путем добавления к параметру типа префикса `-`. +Это допустимо, так как параметр типа используется только _в позиции аргумента_. + +Пометка его как контравариантного означает, что можно передать (или вернуть) `Consumer[Item]` там, +где ожидается `Consumer[Buyable]`. +То есть у нас есть отношение подтипа `Consumer[Item] <: Consumer[Buyable]`. +Помните, что для типа `Producer` все было наоборот, и у нас был `Producer[Buyable] <: Producer[Item]`. + +И в самом деле, это разумно. Метод `Consumer[Item].take` принимает `Item`. +Как вызывающий `take`, мы также можем предоставить `Buyable`, который будет с радостью принят `Consumer[Item]`, +поскольку `Buyable` — это подтип `Item`, то есть, _по крайней мере_, `Item`. + +#### Контравариантные типы для потребителей + +Контравариантные типы встречаются гораздо реже, чем ковариантные типы. +Как и в нашем примере, вы можете думать о них как о «потребителях». +Наиболее важным типом, помеченным как контравариантный, с которым можно столкнуться, является тип функций: + +{% tabs types-variance-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-variance-8 %} + +```scala +trait Function[-A, +B] { + def apply(a: A): B +} +``` + +{% endtab %} + +{% tab 'Scala 3' for=types-variance-8 %} + +```scala +trait Function[-A, +B]: + def apply(a: A): B +``` + +{% endtab %} +{% endtabs %} + +Тип аргумента `A` помечен как контравариантный `A` — он использует значения типа `A`. +Тип результата `B`, напротив, помечен как ковариантный — он создает значения типа `B`. + +Вот несколько примеров, иллюстрирующих отношения подтипов, вызванные аннотациями вариантности функций: + +{% tabs types-variance-9 %} +{% tab 'Scala 2 и 3' %} + +```scala +val f: Function[Buyable, Buyable] = b => b + +// OK - допустимо вернуть Buyable там, где ожидается Item +val g: Function[Buyable, Item] = f + +// OK - допустимо передать аргумент Book туда, где ожидается Buyable +val h: Function[Book, Buyable] = f +``` + +{% endtab %} +{% endtabs %} + +## Резюме + +В этом разделе были рассмотрены три различных вида вариантности: + +- **Producers** обычно ковариантны и помечают свой параметр типа со знаком `+`. + Это справедливо и для неизменяемых коллекций. +- **Consumers** обычно контравариантны и помечают свой параметр типа со знаком `-`. +- Типы, которые являются **одновременно** производителями и потребителями, + должны быть инвариантными и не требуют какой-либо маркировки для параметра своего типа. + В эту категорию, в частности, попадают изменяемые коллекции, такие как `Array`.