From 590a3c5fb97b76b30777364087bb6e77c58b462c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Wed, 16 Aug 2023 09:47:20 +0300 Subject: [PATCH] Add ca-implicit-conversions.md, ca-multiversal-equality.md, ca-summary.md in ru --- .../scala3-book/ca-implicit-conversions.md | 2 +- .../scala3-book/ca-multiversal-equality.md | 2 +- _overviews/scala3-book/ca-summary.md | 2 +- _ru/scala3/book/ca-implicit-conversions.md | 236 ++++++++++++++++++ _ru/scala3/book/ca-multiversal-equality.md | 207 +++++++++++++++ _ru/scala3/book/ca-summary.md | 38 +++ _ru/scala3/book/ca-type-classes.md | 2 +- 7 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 _ru/scala3/book/ca-implicit-conversions.md create mode 100644 _ru/scala3/book/ca-multiversal-equality.md create mode 100644 _ru/scala3/book/ca-summary.md diff --git a/_overviews/scala3-book/ca-implicit-conversions.md b/_overviews/scala3-book/ca-implicit-conversions.md index 16e9dec53..b35902d71 100644 --- a/_overviews/scala3-book/ca-implicit-conversions.md +++ b/_overviews/scala3-book/ca-implicit-conversions.md @@ -2,7 +2,7 @@ title: Implicit Conversions type: section description: This page demonstrates how to implement Implicit Conversions in Scala. -languages: [zh-cn] +languages: [ru, zh-cn] num: 66 previous-page: ca-multiversal-equality next-page: ca-summary diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md index be63a600a..062f23976 100644 --- a/_overviews/scala3-book/ca-multiversal-equality.md +++ b/_overviews/scala3-book/ca-multiversal-equality.md @@ -2,7 +2,7 @@ title: Multiversal Equality type: section description: This page demonstrates how to implement Multiversal Equality in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 65 previous-page: ca-type-classes next-page: ca-implicit-conversions diff --git a/_overviews/scala3-book/ca-summary.md b/_overviews/scala3-book/ca-summary.md index 2a9d28121..f17ffc035 100644 --- a/_overviews/scala3-book/ca-summary.md +++ b/_overviews/scala3-book/ca-summary.md @@ -2,7 +2,7 @@ title: Summary type: section description: This page provides a summary of the Contextual Abstractions lessons. -languages: [zh-cn] +languages: [ru, zh-cn] num: 67 previous-page: ca-implicit-conversions next-page: concurrency diff --git a/_ru/scala3/book/ca-implicit-conversions.md b/_ru/scala3/book/ca-implicit-conversions.md new file mode 100644 index 000000000..f4703dc07 --- /dev/null +++ b/_ru/scala3/book/ca-implicit-conversions.md @@ -0,0 +1,236 @@ +--- +layout: multipage-overview +title: Неявное преобразование типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице демонстрируется, как реализовать неявное преобразование типов в Scala 3. +language: ru +num: 66 +previous-page: ca-multiversal-equality +next-page: ca-summary +--- + +Неявные преобразования — это мощная функция Scala, позволяющая пользователям предоставлять аргумент одного типа, +как если бы он был другого типа, чтобы избежать шаблонного преобразования. + +> Обратите внимание, что в Scala 2 неявные преобразования также использовались для предоставления дополнительных членов +> запечатанным классам (см. [Неявные классы]({% link _overviews/core/implicit-classes.md %})). +> В Scala 3 мы рекомендуем использовать эту функциональность, определяя методы расширения вместо неявных преобразований +> (хотя стандартная библиотека по-прежнему полагается на неявные преобразования по историческим причинам). + +## Пример + +Рассмотрим, например, метод `findUserById`, принимающий параметр типа `Long`: + +{% tabs implicit-conversions-1 %} +{% tab 'Scala 2 и 3' %} + +```scala +def findUserById(id: Long): Option[User] +``` + +{% endtab %} +{% endtabs %} + +Для краткости опустим определение типа `User` - это не имеет значения для нашего примера. + +В Scala есть возможность вызвать метод `findUserById` с аргументом типа `Int` вместо ожидаемого типа `Long`, +потому что аргумент будет неявно преобразован в тип `Long`: + +{% tabs implicit-conversions-2 %} +{% tab 'Scala 2 и 3' %} + +```scala +val id: Int = 42 +findUserById(id) // OK +``` + +{% endtab %} +{% endtabs %} + +Этот код не упадет с ошибкой компиляции “type mismatch: expected `Long`, found `Int`”, +потому что есть неявное преобразование, которое преобразует аргумент `id` в значение типа `Long`. + +## Детальное объяснение + +В этом разделе описывается, как определять и использовать неявные преобразования. + +### Определение неявного преобразования + +{% tabs implicit-conversions-3 class=tabs-scala-version %} + +{% tab 'Scala 2' %} + +В Scala 2 неявное преобразование из типа `S` в тип `T` определяется [неявным классом]({% link _overviews/core/implicit-classes.md %}) `T`, +который принимает один параметр конструктора типа `S`, [неявное значение]({% link _overviews/scala3-book/ca-context-parameters.md %}) +типа функции `S => T` или неявный метод, преобразуемый в значение этого типа. + +Например, следующий код определяет неявное преобразование из `Int` в `Long`: + +```scala +import scala.language.implicitConversions + +implicit def int2long(x: Int): Long = x.toLong +``` + +Это неявный метод, преобразуемый в значение типа `Int => Long`. + +См. раздел "Остерегайтесь силы неявных преобразований" ниже для объяснения пункта `import scala.language.implicitConversions` в начале. +{% endtab %} + +{% tab 'Scala 3' %} +В Scala 3 неявное преобразование типа `S` в тип `T` определяется [`given` экземпляром]({% link _overviews/scala3-book/ca-context-parameters.md %}) +типа `scala.Conversion[S, T]`. +Для совместимости со Scala 2 его также можно определить неявным методом (подробнее читайте во вкладке Scala 2). + +Например, этот код определяет неявное преобразование из `Int` в `Long`: + +```scala +given int2long: Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong +``` + +Как и другие given определения, неявные преобразования могут быть анонимными: + +```scala +given Conversion[Int, Long] with + def apply(x: Int): Long = x.toLong +``` + +Используя псевдоним, это можно выразить более кратко: + +```scala +given Conversion[Int, Long] = (x: Int) => x.toLong +``` + +{% endtab %} + +{% endtabs %} + +### Использование неявного преобразования + +Неявные преобразования применяются в двух случаях: + +1. Если выражение `e` имеет тип `S` и `S` не соответствует ожидаемому типу выражения `T`. +2. В выборе `e.m` с `e` типа `S`, где `S` не определяет `m` + (для поддержки [методов расширения][extension methods] в стиле Scala-2). + +В первом случае ищется конверсия `c`, применимая к `e` и тип результата которой соответствует `T`. + +В примере выше, когда мы передаем аргумент `id` типа `Int` в метод `findUserById`, +вставляется неявное преобразование `int2long(id)`. + +Во втором случае ищется преобразование `c`, применимое к `e` и результат которого содержит элемент с именем `m`. + +Примером является сравнение двух строк `"foo" < "bar"`. +В этом случае `String` не имеет члена `<`, поэтому вставляется неявное преобразование `Predef.augmentString("foo") < "bar"` +(`scala.Predef` автоматически импортируется во все программы Scala.). + +### Как неявные преобразования становятся доступными? + +Когда компилятор ищет подходящие преобразования: + +- во-первых, он смотрит в текущую лексическую область + - неявные преобразования, определенные в текущей области или во внешних областях + - импортированные неявные преобразования + - неявные преобразования, импортированные с помощью импорта подстановочных знаков (только в Scala 2) +- затем он просматривает [сопутствующие объекты][companion objects], _связанные_ с типом аргумента `S` или ожидаемым типом `T`. + Сопутствующие объекты, связанные с типом `X`: + - сам объект-компаньон `X` + - сопутствующие объекты, связанные с любым из унаследованных типов `X` + - сопутствующие объекты, связанные с любым аргументом типа в `X` + - если `X` - это внутренний класс, внешние объекты, в которые он встроен + +Например, рассмотрим неявное преобразование `fromStringToUser`, определенное в объекте `Conversions`: + +{% tabs implicit-conversions-4 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import scala.language.implicitConversions + +object Conversions { + implicit def fromStringToUser(name: String): User = (name: String) => User(name) +} +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +object Conversions: + given fromStringToUser: Conversion[String, User] = (name: String) => User(name) +``` + +{% endtab %} +{% endtabs %} + +Следующие операции импорта эквивалентно передают преобразование в область действия: + +{% tabs implicit-conversions-5 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +import Conversions.fromStringToUser +// или +import Conversions._ +``` + +{% endtab %} +{% tab 'Scala 3' %} + +```scala +import Conversions.fromStringToUser +// или +import Conversions.given +// или +import Conversions.{given Conversion[String, User]} +``` + +Обратите внимание, что в Scala 3 импорт с подстановочными знаками (т.е. `import Conversions.*`) +не импортирует given определения. + +{% endtab %} +{% endtabs %} + +Во вводном примере преобразование из `Int` в `Long` не требует импорта, поскольку оно определено в объекте `Int`, +который является сопутствующим объектом типа `Int`. + +Дополнительная литература: +[Где Scala ищет неявные значения? (в Stackoverflow)](https://stackoverflow.com/a/5598107). + +### Остерегайтесь силы неявных преобразований + +{% tabs implicit-conversions-6 class=tabs-scala-version %} +{% tab 'Scala 2' %} + +Поскольку неявные преобразования могут иметь подводные камни, если используются без разбора, +компилятор предупреждает при компиляции определения неявного преобразования. + +Чтобы отключить предупреждения, выполните одно из следующих действий: + +- Импорт `scala.language.implicitConversions` в область определения неявного преобразования +- Вызвать компилятор с командой `-language:implicitConversions` + +Предупреждение не выдается, когда компилятор применяет преобразование. +{% endtab %} +{% tab 'Scala 3' %} +Поскольку неявные преобразования могут иметь подводные камни, если они используются без разбора, +компилятор выдает предупреждение в двух случаях: + +- при компиляции определения неявного преобразования в стиле Scala 2. +- на стороне вызова, где given экземпляр `scala.Conversion` вставляется как конверсия. + +Чтобы отключить предупреждения, выполните одно из следующих действий: + +- Импортировать `scala.language.implicitConversions` в область: + - определения неявного преобразования в стиле Scala 2 + - стороны вызова, где given экземпляр `scala.Conversion` вставляется как конверсия. +- Вызвать компилятор с командой `-language:implicitConversions` + {% endtab %} + {% endtabs %} + +[extension methods]: {% link _overviews/scala3-book/ca-extension-methods.md %} +[companion objects]: {% link _overviews/scala3-book/domain-modeling-tools.md %}#companion-objects diff --git a/_ru/scala3/book/ca-multiversal-equality.md b/_ru/scala3/book/ca-multiversal-equality.md new file mode 100644 index 000000000..19960bc97 --- /dev/null +++ b/_ru/scala3/book/ca-multiversal-equality.md @@ -0,0 +1,207 @@ +--- +layout: multipage-overview +title: Многостороннее равенство +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице демонстрируется, как реализовать многостороннее равенство в Scala 3. +language: ru +num: 65 +previous-page: ca-type-classes +next-page: ca-implicit-conversions +versionSpecific: true +--- + +Раньше в Scala было _универсальное равенство_ (_universal equality_): +два значения любых типов можно было сравнивать друг с другом с помощью `==` и `!=`. +Это произошло из-за того факта, что `==` и `!=` реализованы в терминах метода `equals` Java, +который также может сравнивать значения любых двух ссылочных типов. + +Универсальное равенство удобно, но оно также опасно, поскольку подрывает безопасность типов. +Например, предположим, что после некоторого рефакторинга осталась ошибочная программа, +в которой значение `y` имеет тип `S` вместо правильного типа `T`: + +```scala +val x = ... // типа T +val y = ... // типа S, но должно быть типа T +x == y // результат проверки типов всегда будет false +``` + +Если `y` сравнивается с другими значениями типа `T`, программа все равно будет проверять тип, +так как значения всех типов можно сравнивать друг с другом. +Но это, вероятно, даст неожиданные результаты и завершится ошибкой во время выполнения. + +Типобезопасный язык программирования может работать лучше, а многостороннее равенство — +это дополнительный способ сделать универсальное равенство более безопасным. +Он использует класс двоичного типа `CanEqual`, чтобы указать, что значения двух заданных типов можно сравнивать друг с другом. + +## Разрешение сравнения экземпляров класса + +По умолчанию в Scala 3 все ещё можно сравнивать на равенство следующим образом: + +```scala +case class Cat(name: String) +case class Dog(name: String) +val d = Dog("Fido") +val c = Cat("Morris") + +d == c // false, но он компилируется +``` + +Но в Scala 3 такие сравнения можно отключить. +При (а) импорте `scala.language.strictEquality` или (б) использовании флага компилятора `-language:strictEquality` +это сравнение больше не компилируется: + +```scala +import scala.language.strictEquality + +val rover = Dog("Rover") +val fido = Dog("Fido") +println(rover == fido) // ошибка компиляции + +// сообщение об ошибке компиляции: +// Values of types Dog and Dog cannot be compared with == or != +``` + +## Включение сравнений + +Есть два способа включить сравнение с помощью класса типов `CanEqual`. +Для простых случаев класс может _выводиться_ (_derive_) от класса `CanEqual`: + +```scala +// Способ 1 +case class Dog(name: String) derives CanEqual +``` + +Как вы вскоре увидите, когда нужна большая гибкость, вы также можете использовать следующий синтаксис: + +```scala +// Способ 2 +case class Dog(name: String) +given CanEqual[Dog, Dog] = CanEqual.derived +``` + +Любой из этих двух подходов позволяет сравнивать экземпляры `Dog` друг с другом. + +## Более реалистичный пример + +В более реалистичном примере представим, что есть книжный интернет-магазин +и мы хотим разрешить или запретить сравнение бумажных, печатных и аудиокниг. +В Scala 3 для начала необходимо включить многостороннее равенство: + +```scala +// [1] добавить этот импорт или command line flag: -language:strictEquality +import scala.language.strictEquality +``` + +Затем создать объекты домена: + +```scala +// [2] создание иерархии классов +trait Book: + def author: String + def title: String + def year: Int + +case class PrintedBook( + author: String, + title: String, + year: Int, + pages: Int +) extends Book + +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book +``` + +Наконец, используем `CanEqual`, чтобы определить, какие сравнения необходимо разрешить: + +```scala +// [3] создайте экземпляры класса типов, чтобы определить разрешенные сравнения. +// разрешено `PrintedBook == PrintedBook` +// разрешено `AudioBook == AudioBook` +given CanEqual[PrintedBook, PrintedBook] = CanEqual.derived +given CanEqual[AudioBook, AudioBook] = CanEqual.derived + +// [4a] сравнение двух печатных книг разрешено +val p1 = PrintedBook("1984", "George Orwell", 1961, 328) +val p2 = PrintedBook("1984", "George Orwell", 1961, 328) +println(p1 == p2) // true + +// [4b] нельзя сравнивать печатную книгу и аудиокнигу +val pBook = PrintedBook("1984", "George Orwell", 1961, 328) +val aBook = AudioBook("1984", "George Orwell", 2006, 682) +println(pBook == aBook) // ошибка компиляции +``` + +Последняя строка кода приводит к следующему сообщению компилятора об ошибке: + +``` +Values of types PrintedBook and AudioBook cannot be compared with == or != +``` + +Вот как многостороннее равенство отлавливает недопустимые сравнения типов во время компиляции. + +### Включение “PrintedBook == AudioBook” + +Если есть необходимость разрешить сравнение "печатной книги" (`PrintedBook`) с аудио-книгой (`AudioBook`), +то достаточно создать следующие два дополнительных сравнения равенства: + +```scala +// разрешить `PrintedBook == AudioBook` и `AudioBook == PrintedBook` +given CanEqual[PrintedBook, AudioBook] = CanEqual.derived +given CanEqual[AudioBook, PrintedBook] = CanEqual.derived +``` + +Теперь можно сравнивать печатную книгу с аудио-книгой без ошибки компилятора: + +```scala +println(pBook == aBook) // false +println(aBook == pBook) // false +``` + +#### Внедрите "equals", чтобы они действительно работали + +Хотя эти сравнения теперь разрешены, они всегда будут ложными, +потому что их методы `equals` не знают, как проводить подобные сравнения. +Чтобы доработать сравнение, можно переопределить методы `equals` для каждого класса. +Например, если переопределить метод `equals` для `AudioBook`: + +```scala +case class AudioBook( + author: String, + title: String, + year: Int, + lengthInMinutes: Int +) extends Book: + // переопределить, чтобы разрешить сравнение AudioBook с PrintedBook + override def equals(that: Any): Boolean = that match + case a: AudioBook => + this.author == a.author + && this.title == a.title + && this.year == a.year + && this.lengthInMinutes == a.lengthInMinutes + case p: PrintedBook => + this.author == p.author && this.title == p.title + case _ => + false +``` + +Теперь можно сравнить `AudioBook` с `PrintedBook`: + +```scala +println(aBook == pBook) // true (работает из-за переопределенного `equals` в `AudioBook`) +println(pBook == aBook) // false +``` + +Книга `PrintedBook` не имеет метода `equals`, поэтому второе сравнение возвращает `false`. +Чтобы включить это сравнение, достаточно переопределить метод `equals` в `PrintedBook`. + +Вы можете найти дополнительную информацию о [многостороннем равенстве][ref-equal] в справочной документации. + +[ref-equal]: {{ site.scala3ref }}/contextual/multiversal-equality.html diff --git a/_ru/scala3/book/ca-summary.md b/_ru/scala3/book/ca-summary.md new file mode 100644 index 000000000..3c56a2913 --- /dev/null +++ b/_ru/scala3/book/ca-summary.md @@ -0,0 +1,38 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен краткий обзор уроков по контекстуальным абстракциям. +language: ru +num: 67 +previous-page: ca-implicit-conversions +next-page: +--- + +В этой главе представлено введение в большинство тем контекстных абстракций, в том числе: + +- [Методы расширения](ca-extension-methods.html) +- [Экземпляры given и параметры контекста](ca-context-parameters.html) +- [Контекстные границы](ca-context-bounds.html) +- [Импорт given](ca-given-imports.html) +- [Классы типов](ca-type-classes.html) +- [Многостороннее равенство](ca-multiversal-equality.html) +- [Неявные преобразования](ca-implicit-conversions.html) + +Все эти функции являются вариантами основной идеи **вывода термов**: +учитывая тип, компилятор синтезирует “канонический” терм, который имеет этот тип. + +Несколько более сложных тем здесь не рассматриваются, в том числе: + +- Условные given экземпляры +- Вывод класса типов +- Контекстные функции +- Контекстные параметры по имени +- Связь с имплицитами Scala 2 + +Эти темы подробно обсуждаются в [справочной документации][ref]. + +[ref]: {{ site.scala3ref }}/contextual diff --git a/_ru/scala3/book/ca-type-classes.md b/_ru/scala3/book/ca-type-classes.md index 95b2d83ec..5da4f9468 100644 --- a/_ru/scala3/book/ca-type-classes.md +++ b/_ru/scala3/book/ca-type-classes.md @@ -9,7 +9,7 @@ description: В этой главе демонстрируется создан language: ru num: 64 previous-page: ca-given-imports -next-page: +next-page: ca-multiversal-equality --- Класс типов (_type class_) — это абстрактный параметризованный тип,