diff --git a/_overviews/scala3-book/types-intersection.md b/_overviews/scala3-book/types-intersection.md index 8c01dd9b33..66a6f3ec0d 100644 --- a/_overviews/scala3-book/types-intersection.md +++ b/_overviews/scala3-book/types-intersection.md @@ -2,7 +2,7 @@ title: Intersection Types type: section description: This section introduces and demonstrates intersection types in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 50 previous-page: types-generics next-page: types-union diff --git a/_overviews/scala3-book/types-union.md b/_overviews/scala3-book/types-union.md index 271c9a0148..50c5919980 100644 --- a/_overviews/scala3-book/types-union.md +++ b/_overviews/scala3-book/types-union.md @@ -2,7 +2,7 @@ title: Union Types type: section description: This section introduces and demonstrates union types in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 51 previous-page: types-intersection next-page: types-adts-gadts diff --git a/_ru/scala3/book/types-generics.md b/_ru/scala3/book/types-generics.md index 799a85e3d5..653d6cacbd 100644 --- a/_ru/scala3/book/types-generics.md +++ b/_ru/scala3/book/types-generics.md @@ -9,7 +9,7 @@ description: В этом разделе представлены парамет language: ru num: 49 previous-page: types-inferred -next-page: +next-page: types-intersection --- Универсальные (_generic_) классы (или trait-ы) принимают тип в качестве _параметра_ в квадратных скобках `[...]`. diff --git a/_ru/scala3/book/types-intersection.md b/_ru/scala3/book/types-intersection.md new file mode 100644 index 0000000000..6fa78e33ab --- /dev/null +++ b/_ru/scala3/book/types-intersection.md @@ -0,0 +1,76 @@ +--- +layout: multipage-overview +title: Пересечение типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены пересечение типов в Scala 3. +language: ru +num: 50 +previous-page: types-generics +next-page: types-union +--- + +Только в Scala 3 + +Используемый для типов оператор `&` создает так называемый _тип пересечения_ (_intersection type_). +Тип `A & B` представляет собой значения, которые **одновременно** относятся как к типу `A`, так и к типу `B`. +Например, в следующем примере используется тип пересечения `Resettable & Growable[String]`: + +{% tabs intersection-reset-grow %} + +{% tab 'Только в Scala 3' %} + +```scala +trait Resettable: + def reset(): Unit + +trait Growable[A]: + def add(a: A): Unit + +def f(x: Resettable & Growable[String]): Unit = + x.reset() + x.add("first") +``` + +{% endtab %} + +{% endtabs %} + +В методе `f` в этом примере параметр `x` должен быть _одновременно_ как `Resettable`, так и `Growable[String]`. + +Все _члены_ типа пересечения `A & B` являются типом `A` и типом `B`. +Следовательно, как показано, для `Resettable & Growable[String]` доступны методы `reset` и `add`. + +Пересечение типов может быть полезно для _структурного_ описания требований. +В примере выше для `f` мы прямо заявляем, что нас устраивает любое значение для `x`, +если оно является подтипом как `Resettable`, так и `Growable`. +**Нет** необходимости создавать _номинальный_ вспомогательный trait, подобный следующему: + +{% tabs normal-trait class=tabs-scala-version %} +{% tab 'Scala 2' %} + +```scala +trait Both[A] extends Resettable with Growable[A] +def f(x: Both[String]): Unit +``` + +{% endtab %} + +{% tab 'Scala 3' %} + +```scala +trait Both[A] extends Resettable, Growable[A] +def f(x: Both[String]): Unit +``` + +{% endtab %} +{% endtabs %} + +Существует важное различие между двумя вариантами определения `f`: +в то время как оба позволяют вызывать `f` с экземплярами `Both`, +только первый позволяет передавать экземпляры, +которые являются подтипами `Resettable` и `Growable[String]`, _но не_ `Both[String]`. + +> Обратите внимание, что `&` _коммутативно_: `A & B` имеет тот же тип, что и `B & A`. diff --git a/_ru/scala3/book/types-union.md b/_ru/scala3/book/types-union.md new file mode 100644 index 0000000000..35a0f440f6 --- /dev/null +++ b/_ru/scala3/book/types-union.md @@ -0,0 +1,111 @@ +--- +layout: multipage-overview +title: Объединение типов +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлены объединение типов в Scala 3. +language: ru +num: 51 +previous-page: types-intersection +next-page: +--- + +Только в Scala 3 + +Используемый для типов `|` оператор создает так называемый _тип объединения_ (_union type_). +Тип `А | B` представляет значения, которые относятся **либо** к типу `A`, **либо** к типу `B`. + +В следующем примере метод `help` принимает параметр с именем `id` типа объединения `Username | Password`, +который может быть либо `Username`, либо `Password`: + +```scala +case class Username(name: String) +case class Password(hash: Hash) + +def help(id: Username | Password) = + val user = id match + case Username(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + // дальнейший код ... +``` + +Мы реализуем метод `help`, разделяя две альтернативы с использованием сопоставления с образцом. + +Этот код является гибким и типобезопасным решением. +Если попытаться передать тип, отличный от `Username` или `Password`, компилятор пометит это как ошибку: + +```scala +help("hi") // error: Found: ("hi" : String) + // Required: Username | Password +``` + +Ошибка также будет получена, если попытаться добавить `case` в выражение `match`, +которое не соответствует типам `Username` или `Password`: + +```scala +case 1.0 => ??? // Ошибка: это строка не компилируется +``` + +### Альтернатива объединенным типам + +Как показано, объединенные типы могут использоваться для представления вариантов нескольких разных типов, +не требуя, чтобы эти типы были частью специально созданной иерархии классов. + +#### Предварительное планирование иерархии классов + +Другие языки требуют предварительного планирования иерархии классов, как показано в следующем примере: + +```scala +trait UsernameOrPassword +case class Username(name: String) extends UsernameOrPassword +case class Password(hash: Hash) extends UsernameOrPassword +def help(id: UsernameOrPassword) = ... +``` + +Предварительное планирование не очень хорошо масштабируется, +поскольку, например, требования пользователей API могут быть непредсказуемыми. +Кроме того, загромождение иерархии типов маркерами типа `UsernameOrPassword` затрудняет чтение кода. + +#### Теговые объединения + +Другой альтернативой является задание отдельного типа перечисления, например: + +```scala +enum UsernameOrPassword: + case IsUsername(u: Username) + case IsPassword(p: Password) +``` + +Перечисление `UsernameOrPassword` представляет собой _помеченное_ (_tagged_) объединение `Username` и `Password`. +Однако этот способ моделирования объединения требует _явной упаковки и распаковки_, +и, например, `Username` **не** является подтипом `UsernameOrPassword`. + +### Вывод типов объединения + +Компилятор присваивает типу объединения выражение, _только если_ такой тип явно задан. +Например, рассмотрим такие значения: + +```scala +val name = Username("Eve") // name: Username = Username(Eve) +val password = Password(123) // password: Password = Password(123) +``` + +В этом REPL примере показано, +как можно использовать тип объединения при привязке переменной к результату выражения `if`/`else`: + +``` +scala> val a = if true then name else password +val a: Object = Username(Eve) + +scala> val b: Password | Username = if true then name else password +val b: Password | Username = Username(Eve) +``` + +Типом `a` является `Object`, который является супертипом `Username` и `Password`, +но не _наименьшим_ супертипом, `Password | Username`. +Если необходим наименьший супертип, его нужно указать явно, как это делается для `b`. + +> Типы объединения являются двойственными типам пересечения. +> И как `&` с типами пересечения, `|` также коммутативен: `A | B` того же типа, что и `B | А`.