diff --git a/_overviews/scala3-book/collections-classes.md b/_overviews/scala3-book/collections-classes.md index b9ba20afc8..c68bc9e035 100644 --- a/_overviews/scala3-book/collections-classes.md +++ b/_overviews/scala3-book/collections-classes.md @@ -2,7 +2,7 @@ title: Collections Types type: section description: This page introduces the common Scala 3 collections types and some of their methods. -languages: [zh-cn] +languages: [ru, zh-cn] num: 37 previous-page: collections-intro next-page: collections-methods diff --git a/_overviews/scala3-book/collections-intro.md b/_overviews/scala3-book/collections-intro.md index 8a0ae572ba..befedef0e6 100644 --- a/_overviews/scala3-book/collections-intro.md +++ b/_overviews/scala3-book/collections-intro.md @@ -2,7 +2,7 @@ title: Scala Collections type: chapter description: This page provides and introduction to the common collections classes and their methods in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 36 previous-page: packaging-imports next-page: collections-classes diff --git a/_overviews/scala3-book/collections-methods.md b/_overviews/scala3-book/collections-methods.md index 35fcb72b70..f165a2c0e9 100644 --- a/_overviews/scala3-book/collections-methods.md +++ b/_overviews/scala3-book/collections-methods.md @@ -2,7 +2,7 @@ title: Collections Methods type: section description: This page demonstrates the common methods on the Scala 3 collections classes. -languages: [zh-cn] +languages: [ru, zh-cn] num: 38 previous-page: collections-classes next-page: collections-summary diff --git a/_overviews/scala3-book/collections-summary.md b/_overviews/scala3-book/collections-summary.md index 446c8b90c9..c8ac5512fa 100644 --- a/_overviews/scala3-book/collections-summary.md +++ b/_overviews/scala3-book/collections-summary.md @@ -2,7 +2,7 @@ title: Summary type: section description: This page provides a summary of the Collections chapter. -languages: [zh-cn] +languages: [ru, zh-cn] num: 39 previous-page: collections-methods next-page: fp-intro diff --git a/_overviews/scala3-book/fp-functional-error-handling.md b/_overviews/scala3-book/fp-functional-error-handling.md index edc5f8a5ed..829845eaf8 100644 --- a/_overviews/scala3-book/fp-functional-error-handling.md +++ b/_overviews/scala3-book/fp-functional-error-handling.md @@ -2,7 +2,7 @@ title: Functional Error Handling type: section description: This section provides an introduction to functional error handling in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 45 previous-page: fp-functions-are-values next-page: fp-summary diff --git a/_overviews/scala3-book/fp-functions-are-values.md b/_overviews/scala3-book/fp-functions-are-values.md index b4df001cce..48f7e4ca3a 100644 --- a/_overviews/scala3-book/fp-functions-are-values.md +++ b/_overviews/scala3-book/fp-functions-are-values.md @@ -2,7 +2,7 @@ title: Functions Are Values type: section description: This section looks at the use of functions as values in functional programming. -languages: [zh-cn] +languages: [ru, zh-cn] num: 44 previous-page: fp-pure-functions next-page: fp-functional-error-handling diff --git a/_overviews/scala3-book/fp-immutable-values.md b/_overviews/scala3-book/fp-immutable-values.md index 6202943906..413d2d0495 100644 --- a/_overviews/scala3-book/fp-immutable-values.md +++ b/_overviews/scala3-book/fp-immutable-values.md @@ -2,7 +2,7 @@ title: Immutable Values type: section description: This section looks at the use of immutable values in functional programming. -languages: [zh-cn] +languages: [ru, zh-cn] num: 42 previous-page: fp-what-is-fp next-page: fp-pure-functions diff --git a/_overviews/scala3-book/fp-intro.md b/_overviews/scala3-book/fp-intro.md index 56c43362c8..7d6e6f4114 100644 --- a/_overviews/scala3-book/fp-intro.md +++ b/_overviews/scala3-book/fp-intro.md @@ -2,7 +2,7 @@ title: Functional Programming type: chapter description: This chapter provides an introduction to functional programming in Scala 3. -languages: [zh-cn] +languages: [ru, zh-cn] num: 40 previous-page: collections-summary next-page: fp-what-is-fp diff --git a/_overviews/scala3-book/fp-pure-functions.md b/_overviews/scala3-book/fp-pure-functions.md index fd7c356421..754d6f46a5 100644 --- a/_overviews/scala3-book/fp-pure-functions.md +++ b/_overviews/scala3-book/fp-pure-functions.md @@ -2,7 +2,7 @@ title: Pure Functions type: section description: This section looks at the use of pure functions in functional programming. -languages: [zh-cn] +languages: [ru, zh-cn] num: 43 previous-page: fp-immutable-values next-page: fp-functions-are-values diff --git a/_overviews/scala3-book/fp-summary.md b/_overviews/scala3-book/fp-summary.md index 7a9caff971..55233f851c 100644 --- a/_overviews/scala3-book/fp-summary.md +++ b/_overviews/scala3-book/fp-summary.md @@ -2,7 +2,7 @@ title: Summary type: section description: This section summarizes the previous functional programming sections. -languages: [zh-cn] +languages: [ru, zh-cn] num: 46 previous-page: fp-functional-error-handling next-page: types-introduction diff --git a/_overviews/scala3-book/fp-what-is-fp.md b/_overviews/scala3-book/fp-what-is-fp.md index 034f2ecc16..1e37279ae0 100644 --- a/_overviews/scala3-book/fp-what-is-fp.md +++ b/_overviews/scala3-book/fp-what-is-fp.md @@ -2,7 +2,7 @@ title: What is Functional Programming? type: section description: This section provides an answer to the question, what is functional programming? -languages: [zh-cn] +languages: [ru, zh-cn] num: 41 previous-page: fp-intro next-page: fp-immutable-values diff --git a/_overviews/scala3-book/packaging-imports.md b/_overviews/scala3-book/packaging-imports.md index 6cd3835078..74d70948f0 100644 --- a/_overviews/scala3-book/packaging-imports.md +++ b/_overviews/scala3-book/packaging-imports.md @@ -2,7 +2,7 @@ title: Packaging and Imports type: chapter description: A discussion of using packages and imports to organize your code, build related modules of code, control scope, and help prevent namespace collisions. -languages: [zh-cn] +languages: [ru, zh-cn] num: 35 previous-page: fun-summary next-page: collections-intro diff --git a/_ru/scala3/book/collections-classes.md b/_ru/scala3/book/collections-classes.md new file mode 100644 index 0000000000..53f169249f --- /dev/null +++ b/_ru/scala3/book/collections-classes.md @@ -0,0 +1,971 @@ +--- +layout: multipage-overview +title: Типы коллекций +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлены общие типы коллекций Scala 3 и некоторые из их методов. +language: ru +num: 37 +previous-page: collections-intro +next-page: collections-methods +--- + + +На этой странице показаны общие коллекции Scala 3 и сопутствующие им методы. +Scala поставляется с большим количеством типов коллекций, на изучение которых может уйти время, +поэтому желательно начать с нескольких из них, а затем использовать остальные по мере необходимости. +Точно так же у каждого типа коллекции есть десятки методов, облегчающих разработку, +поэтому лучше начать изучение лишь с небольшого количества. + +В этом разделе представлены наиболее распространенные типы и методы коллекций, +которые вам понадобятся для начала работы. + +В конце этого раздела представлены дополнительные ссылки, для более глубокого изучения коллекций. + +## Три основные категории коллекций + +Для коллекций Scala можно выделить три основные категории: + +- **Последовательности** (**Sequences**/**Seq**) представляют собой последовательный набор элементов + и могут быть _индексированными_ (как массив) или _линейными_ (как связанный список) +- **Мапы** (**Maps**) содержат набор пар ключ/значение, например Java `Map`, Python dictionary или Ruby `Hash` +- **Множества** (**Sets**) — это неупорядоченный набор уникальных элементов + +Все они являются базовыми типами и имеют подтипы подходящие под конкретные задачи, +таких как параллелизм (concurrency), кэширование (caching) и потоковая передача (streaming). +В дополнение к этим трем основным категориям существуют и другие полезные типы коллекций, +включая диапазоны (ranges), стеки (stacks) и очереди (queues). + + +### Иерархия коллекций + +В качестве краткого обзора следующие три рисунка показывают иерархию классов и трейтов в коллекциях Scala. + +На первом рисунке показаны типы коллекций в пакете _scala.collection_. +Все это высокоуровневые абстрактные классы или трейты, которые обычно имеют _неизменяемые_ и _изменяемые_ реализации. + +![General collection hierarchy][collections1] + +На этом рисунке показаны все коллекции в пакете _scala.collection.immutable_: + +![Immutable collection hierarchy][collections2] + +А на этом рисунке показаны все коллекции в пакете _scala.collection.mutable_: + +![Mutable collection hierarchy][collections3] + +В следующих разделах представлены некоторые из распространенных типов. + +## Общие коллекции + +Основные коллекции, используемые чаще всего: + +| Тип коллекции | Неизменяемая | Изменяемая | Описание | +|----------------|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `List` | ✓ | | Линейная неизменяемая последовательность (связный список) | +| `Vector` | ✓ | | Индексированная неизменяемая последовательность | +| `LazyList` | ✓ | | Ленивый неизменяемый связанный список, элементы которого вычисляются только тогда, когда они необходимы; подходит для больших или бесконечных последовательностей. | +| `ArrayBuffer` | | ✓ | Подходящий тип для изменяемой индексированной последовательности | +| `ListBuffer` | | ✓ | Используется, когда вам нужен изменяемый список; обычно преобразуется в `List` | +| `Map` | ✓ | ✓ | Итерируемая коллекция, состоящая из пар ключей и значений | +| `Set` | ✓ | ✓ | Итерируемая коллекция без повторяющихся элементов | + +Как показано, `Map` и `Set` бывают как изменяемыми, так и неизменяемыми. + +Основы каждого типа демонстрируются в следующих разделах. + +> В Scala _буфер_ (_buffer_), такой как `ArrayBuffer` или `ListBuffer`, представляет собой последовательность, +> которая может увеличиваться и уменьшаться. + +### Примечание о неизменяемых коллекциях + +В последующих разделах всякий раз, когда используется слово _immutable_, можно с уверенностью сказать, +что тип предназначен для использования в стиле _функционального программирования_ (ФП). +С помощью таких типов коллекция не меняется, +а при вызове функциональных методов возвращается новый результат - новая коллекция. + +## Выбор последовательности + +При выборе _последовательности_ (последовательной коллекции элементов) нужно руководствоваться двумя основными вопросами: + +- должна ли последовательность индексироваться (как массив), обеспечивая быстрый доступ к любому элементу, + или она должна быть реализована как линейный связанный список? +- необходима изменяемая или неизменяемая коллекция? + +Рекомендуемые универсальные последовательности: + +| Тип\Категория | Неизменяемая | Изменяемая | +|-----------------------------|--------------|---------------| +| индексируемая | `Vector` | `ArrayBuffer` | +| линейная (связанный список) | `List` | `ListBuffer` | + +Например, если нужна неизменяемая индексированная коллекция, в общем случае следует использовать `Vector`. +И наоборот, если нужна изменяемая индексированная коллекция, используйте `ArrayBuffer`. + +> `List` и `Vector` часто используются при написании кода в функциональном стиле. +> `ArrayBuffer` обычно используется при написании кода в императивном стиле. +> `ListBuffer` используется тогда, когда стили смешиваются, например, при создании списка. + +Следующие несколько разделов кратко демонстрируют типы `List`, `Vector` и `ArrayBuffer`. + + +## `List` + +[List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +представляет собой линейную неизменяемую последовательность. +Каждый раз, когда в список добавляются или удаляются элементы, по сути создается новый список из существующего. + +### Создание списка + +`List` можно создать различными способами: + +{% tabs list-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints = List(1, 2, 3) +val names = List("Joel", "Chris", "Ed") + +// другой путь создания списка List +val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil +``` +{% endtab %} + +{% endtabs %} + +При желании тип списка можно объявить, хотя обычно в этом нет необходимости: + +{% tabs list-type %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints: List[Int] = List(1, 2, 3) +val names: List[String] = List("Joel", "Chris", "Ed") +``` +{% endtab %} + +{% endtabs %} + +Одно исключение — когда в коллекции смешанные типы; в этом случае тип желательно указывать явно: + +{% tabs list-mixed-types class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val things: List[Any] = List(1, "two", 3.0) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val things: List[String | Int | Double] = List(1, "two", 3.0) // с типами объединения +val thingsAny: List[Any] = List(1, "two", 3.0) // с Any +``` +{% endtab %} + +{% endtabs %} + +### Добавление элементов в список + +Поскольку `List` неизменяем, в него нельзя добавлять новые элементы. +Вместо этого создается новый список с добавленными к существующему списку элементами. +Например, учитывая этот `List`: + +{% tabs adding-elements-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Для _добавления_ (_prepend_) к началу списка одного элемента используется метод `::`, для добавления нескольких — `:::`, как показано здесь: + +{% tabs adding-elements-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val b = 0 :: a // List(0, 1, 2, 3) +val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Также можно _добавить_ (_append_) элементы в конец `List`, но, поскольку `List` является односвязным, +следует добавлять к нему элементы только в начало; +добавление элементов в конец списка — относительно медленная операция, +особенно при работе с большими последовательностями. + +> Совет: если необходимо добавлять к неизменяемой последовательности элементы в начало и конец, используйте `Vector`. + +Поскольку `List` является связанным списком, +крайне нежелательно пытаться получить доступ к элементам больших списков по значению их индекса. +Например, если есть `List` с миллионом элементов, доступ к такому элементу, как `myList(999_999)`, +займет относительно много времени, потому что этот запрос должен пройти почти через все элементы. +Если есть большая коллекция и необходимо получать доступ к элементам по их индексу, то +вместо `List` используйте `Vector` или `ArrayBuffer`. + +### Как запомнить названия методов + +В методах Scala символ `:` представляет сторону, на которой находится последовательность, +поэтому, когда используется метод `+:`, список нужно указывать справа: + +{% tabs list-prepending %} + +{% tab 'Scala 2 и 3' %} +```scala +0 +: a +``` +{% endtab %} + +{% endtabs %} + +Аналогично, если используется `:+`, список должен быть слева: + +{% tabs list-appending %} + +{% tab 'Scala 2 и 3' %} +```scala +a :+ 4 +``` +{% endtab %} + +{% endtabs %} + +Хорошей особенностью таких символических имен у методов является то, что они стандартизированы. + +Те же имена методов используются с другими неизменяемыми последовательностями, такими как `Seq` и `Vector`. +Также можно использовать несимволические имена методов для добавления элементов в начало (`a.prepended(4)`) +или конец (`a.appended(4)`). + +### Как пройтись по списку + +Представим, что есть `List` имён: + +{% tabs list-loop-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val names = List("Joel", "Chris", "Ed") +``` +{% endtab %} + +{% endtabs %} + +Напечатать каждое имя можно следующим способом: + +{% tabs list-loop-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for (name <- names) println(name) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for name <- names do println(name) +``` +{% endtab %} + +{% endtabs %} + +Вот как это выглядит в REPL: + +{% tabs list-loop-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for name <- names do println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% endtabs %} + + +Преимуществом использования выражений вида `for` с коллекциями в том, что Scala стандартизирован, +и один и тот же подход работает со всеми последовательностями, +включая `Array`, `ArrayBuffer`, `List`, `Seq`, `Vector`, `Map`, `Set` и т.д. + +### Немного истории + +Список Scala подобен списку из языка программирования [Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)), +который был впервые представлен в 1958 году. +Действительно, в дополнение к привычному способу создания списка: + +{% tabs list-history-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val ints = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +точно такой же список можно создать следующим образом: + +{% tabs list-history-init2 %} + +{% tab 'Scala 2 и 3' %} +```scala +val list = 1 :: 2 :: 3 :: Nil +``` +{% endtab %} + +{% endtabs %} + +REPL показывает, как это работает: + +{% tabs list-history-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val list = 1 :: 2 :: 3 :: Nil +list: List[Int] = List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Это работает, потому что `List` — односвязный список, оканчивающийся элементом `Nil`, +а `::` — это метод `List`, работающий как оператор “cons” в Lisp. + + +### Отступление: LazyList + +Коллекции Scala также включают [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html), +который представляет собой _ленивый_ неизменяемый связанный список. +Он называется «ленивым» — или нестрогим — потому что вычисляет свои элементы только тогда, когда они необходимы. + +Вы можете увидеть отложенное вычисление `LazyList` в REPL: + +{% tabs lazylist-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val x = LazyList.range(1, Int.MaxValue) +x.take(1) // LazyList() +x.take(5) // LazyList() +x.map(_ + 1) // LazyList() +``` +{% endtab %} + +{% endtabs %} + +Во всех этих примерах ничего не происходит. +Действительно, ничего не произойдет, пока вы не заставите это произойти, например, вызвав метод `foreach`: + +{% tabs lazylist-evaluation-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> x.take(1).foreach(println) +1 +``` +{% endtab %} + +{% endtabs %} + +Дополнительные сведения об использовании, преимуществах и недостатках строгих и нестрогих (ленивых) коллекций +см. в обсуждениях “строгих” и “нестрогих” на странице [Архитектура коллекции в Scala 2.13][strict]. + +## Vector + +[Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) - это индексируемая неизменяемая последовательность. +“Индексируемая” часть описания означает, что она обеспечивает произвольный доступ +и обновление за практически постоянное время, +поэтому можно быстро получить доступ к элементам `Vector` по значению их индекса, +например, получить доступ к `listOfPeople(123_456_789)`. + +В общем, за исключением той разницы, что (а) `Vector` индексируется, а `List` - нет, +и (б) `List` имеет метод `::`, эти два типа работают одинаково, +поэтому мы быстро пробежимся по следующим примерам. + +Вот несколько способов создания `Vector`: + +{% tabs vector-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Vector(1, 2, 3, 4, 5) + +val strings = Vector("one", "two") + +case class Person(name: String) +val people = Vector( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} + +{% endtabs %} + +Поскольку `Vector` неизменяем, в него нельзя добавить новые элементы. +Вместо этого создается новая последовательность, с добавленными к существующему `Vector` в начало или в конец элементами. + +Например, так элементы добавляются в конец: + +{% tabs vector-appending %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = a :+ 4 // Vector(1, 2, 3, 4) +val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5) +``` +{% endtab %} + +{% endtabs %} + +А так - в начало Vector-а: + +{% tabs vector-prepending %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Vector(1,2,3) // Vector(1, 2, 3) +val b = 0 +: a // Vector(0, 1, 2, 3) +val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +В дополнение к быстрому произвольному доступу и обновлениям, `Vector` обеспечивает быстрое добавление в начало и конец. + +> Подробную информацию о производительности `Vector` и других коллекций +> см. [в характеристиках производительности коллекций](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html). + +Наконец, `Vector` в выражениях вида `for` используется точно так же, как `List`, `ArrayBuffer` или любая другая последовательность: + +{% tabs vector-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for (name <- names) println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> val names = Vector("Joel", "Chris", "Ed") +val names: Vector[String] = Vector(Joel, Chris, Ed) + +scala> for name <- names do println(name) +Joel +Chris +Ed +``` +{% endtab %} + +{% endtabs %} + + +## ArrayBuffer + +`ArrayBuffer` используется тогда, когда нужна изменяемая индексированная последовательность общего назначения. +Поскольку `ArrayBuffer` индексирован, произвольный доступ к элементам выполняется быстро. + +### Создание ArrayBuffer + +Чтобы использовать `ArrayBuffer`, его нужно вначале импортировать: + +{% tabs arraybuffer-import %} + +{% tab 'Scala 2 и 3' %} +```scala +import scala.collection.mutable.ArrayBuffer +``` +{% endtab %} + +{% endtabs %} + +Если необходимо начать с пустого `ArrayBuffer`, просто укажите его тип: + +{% tabs arraybuffer-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +var strings = ArrayBuffer[String]() +var ints = ArrayBuffer[Int]() +var people = ArrayBuffer[Person]() +``` +{% endtab %} + +{% endtabs %} + +Если известен примерный размер `ArrayBuffer`, его можно задать: + +{% tabs list-creation-with-size %} + +{% tab 'Scala 2 и 3' %} +```scala +// готов вместить 100 000 чисел +val buf = new ArrayBuffer[Int](100_000) +``` +{% endtab %} + +{% endtabs %} + +Чтобы создать новый `ArrayBuffer` с начальными элементами, +достаточно просто указать начальные элементы, как для `List` или `Vector`: + +{% tabs arraybuffer-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) +val people = ArrayBuffer( + Person("Bert"), + Person("Ernie"), + Person("Grover") +) +``` +{% endtab %} + +{% endtabs %} + +### Добавление элементов в ArrayBuffer + +Новые элементы добавляются в `ArrayBuffer` с помощью методов `+=` и `++=`. +Также можно использовать текстовый аналог: `append`, `appendAll`, `insert`, `insertAll`, `prepend` и `prependAll`. +Вот несколько примеров с `+=` и `++=`: + +{% tabs arraybuffer-add %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3) +nums += 4 // ArrayBuffer(1, 2, 3, 4) +nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6) +``` +{% endtab %} + +{% endtabs %} + +### Удаление элементов из ArrayBuffer + +`ArrayBuffer` является изменяемым, +поэтому у него есть такие методы, как `-=`, `--=`, `clear`, `remove` и другие. +Примеры с `-=` и `--=`: + +{% tabs arraybuffer-remove %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g) +a -= 'a' // ArrayBuffer(b, c, d, e, f, g) +a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g) +a --= Set('d', 'e') // ArrayBuffer(f, g) +``` +{% endtab %} + +{% endtabs %} + +### Обновление элементов в ArrayBuffer + +Элементы в `ArrayBuffer` можно обновлять, либо переназначать: + +{% tabs arraybuffer-update %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4) +a(2) = 50 // ArrayBuffer(1, 2, 50, 4) +a.update(0, 10) // ArrayBuffer(10, 2, 50, 4) +``` +{% endtab %} + +{% endtabs %} + + + +## Maps + +`Map` — это итерируемая коллекция, состоящая из пар ключей и значений. +В Scala есть как изменяемые, так и неизменяемые типы `Map`. +В этом разделе показано, как использовать _неизменяемый_ `Map`. + +### Создание неизменяемой Map + +Неизменяемая `Map` создается следующим образом: + +{% tabs map-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) +``` +{% endtab %} + +{% endtabs %} + +Перемещаться по элементам `Map` используя выражение `for` можно следующим образом: + +{% tabs map-loop class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} + +{% endtabs %} + +REPL показывает, как это работает: + +{% tabs map-repl class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for ((k, v) <- states) println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for (k, v) <- states do println(s"key: $k, value: $v") +key: AK, value: Alaska +key: AL, value: Alabama +key: AZ, value: Arizona +``` +{% endtab %} + +{% endtabs %} + +### Доступ к элементам Map + +Доступ к элементам `Map` осуществляется через указание в скобках значения ключа: + +{% tabs map-access-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val ak = states("AK") // ak: String = Alaska +val al = states("AL") // al: String = Alabama +``` +{% endtab %} + +{% endtabs %} + +На практике также используются такие методы, как `keys`, `keySet`, `keysIterator`, `for` выражения +и функции высшего порядка, такие как `map`, для работы с ключами и значениями `Map`. + +### Добавление элемента в Map + +При добавлении элементов в неизменяемую мапу с помощью `+` и `++`, создается новая мапа: + +{% tabs map-add-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map(1 -> "one") // a: Map(1 -> one) +val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two) +val c = b ++ Seq( + 3 -> "three", + 4 -> "four" +) +// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four) +``` +{% endtab %} + +{% endtabs %} + +### Удаление элементов из Map + +Элементы удаляются с помощью методов `-` или `--`. +В случае неизменяемой `Map` создается новый экземпляр, который нужно присвоить новой переменной: + +{% tabs map-remove-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three", + 4 -> "four" +) + +val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three) +val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two) +``` +{% endtab %} + +{% endtabs %} + +### Обновление элементов в Map + +Чтобы обновить элементы на неизменяемой `Map`, используется метод `update` (или оператор `+`): + +{% tabs map-update-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Map( + 1 -> "one", + 2 -> "two", + 3 -> "three" +) + +val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!) +val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three) +``` +{% endtab %} + +{% endtabs %} + +### Перебор элементов в Map + +Элементы в `Map` можно перебрать с помощью выражения `for`, как и для остальных коллекций: + +{% tabs map-traverse class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for ((k, v) <- states) println(s"key: $k, value: $v") +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val states = Map( + "AK" -> "Alaska", + "AL" -> "Alabama", + "AZ" -> "Arizona" +) + +for (k, v) <- states do println(s"key: $k, value: $v") +``` +{% endtab %} + +{% endtabs %} + +Существует _много_ способов работы с ключами и значениями на `Map`. +Общие методы `Map` включают `foreach`, `map`, `keys` и `values`. + +В Scala есть много других специализированных типов `Map`, +включая `CollisionProofHashMap`, `HashMap`, `LinkedHashMap`, `ListMap`, `SortedMap`, `TreeMap`, `WeakHashMap` и другие. + + +## Работа с множествами + +Множество ([Set]({{site.baseurl}}/overviews/collections-2.13/sets.html)) - итерируемая коллекция без повторяющихся элементов. + +В Scala есть как изменяемые, так и неизменяемые типы `Set`. +В этом разделе демонстрируется _неизменяемое_ множество. + +### Создание множества + +Создание нового пустого множества: + +{% tabs set-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Set[Int]() +val letters = Set[Char]() +``` +{% endtab %} + +{% endtabs %} + +Создание множества с исходными данными: + +{% tabs set-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3) +val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c') +``` +{% endtab %} + +{% endtabs %} + + +### Добавление элементов в множество + +В неизменяемое множество новые элементы добавляются с помощью `+` и `++`, результат присваивается новой переменной: + +{% tabs set-add-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Set(1, 2) // Set(1, 2) +val b = a + 3 // Set(1, 2, 3) +val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4) +``` +{% endtab %} + +{% endtabs %} + +Стоит отметить, что повторяющиеся элементы не добавляются в множество, +а также, что порядок элементов произвольный. + + +### Удаление элементов из множества + +Элементы из множества удаляются с помощью методов `-` и `--`, результат также должен присваиваться новой переменной: + +{% tabs set-remove-element %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4) +val b = a - 5 // HashSet(1, 2, 3, 4) +val c = b -- Seq(3, 4) // HashSet(1, 2) +``` +{% endtab %} + +{% endtabs %} + + + +## Диапазон (Range) + +`Range` часто используется для заполнения структур данных и для `for` выражений. +Эти REPL примеры демонстрируют, как создавать диапазоны: + +{% tabs range-init %} + +{% tab 'Scala 2 и 3' %} +```scala +1 to 5 // Range(1, 2, 3, 4, 5) +1 until 5 // Range(1, 2, 3, 4) +1 to 10 by 2 // Range(1, 3, 5, 7, 9) +'a' to 'c' // NumericRange(a, b, c) +``` +{% endtab %} + +{% endtabs %} + +Range можно использовать для заполнения коллекций: + +{% tabs range-conversion %} + +{% tab 'Scala 2 и 3' %} +```scala +val x = (1 to 5).toList // List(1, 2, 3, 4, 5) +val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5) +``` +{% endtab %} + +{% endtabs %} + +Они также используются в `for` выражениях: + +{% tabs range-iteration class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +scala> for (i <- 1 to 3) println(i) +1 +2 +3 +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +scala> for i <- 1 to 3 do println(i) +1 +2 +3 +``` +{% endtab %} + +{% endtabs %} + +Во многих коллекциях есть метод `range`: + +{% tabs range-methods %} + +{% tab 'Scala 2 и 3' %} +```scala +Vector.range(1, 5) // Vector(1, 2, 3, 4) +List.range(1, 10, 2) // List(1, 3, 5, 7, 9) +Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4) +``` +{% endtab %} + +{% endtabs %} + +Диапазоны также полезны для создания тестовых коллекций: + +{% tabs range-tests %} + +{% tab 'Scala 2 и 3' %} +```scala +val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10) +val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9) +val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0) + +// Создание Map +val map = (1 to 3).map(e => (e,s"$e")).toMap +// map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3") +``` +{% endtab %} + +{% endtabs %} + + +## Больше деталей + +Если вам нужна дополнительная информация о специализированных коллекциях, см. следующие ресурсы: + +- [Конкретные неизменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html) +- [Конкретные изменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html) +- [Как устроены коллекции? Какую из них следует выбрать?](https://docs.scala-lang.org/tutorials/FAQ/collections.html) + + + +[strict]: {% link _overviews/core/architecture-of-scala-213-collections.md %} +[collections1]: /resources/images/tour/collections-diagram-213.svg +[collections2]: /resources/images/tour/collections-immutable-diagram-213.svg +[collections3]: /resources/images/tour/collections-mutable-diagram-213.svg diff --git a/_ru/scala3/book/collections-intro.md b/_ru/scala3/book/collections-intro.md new file mode 100644 index 0000000000..92793002fe --- /dev/null +++ b/_ru/scala3/book/collections-intro.md @@ -0,0 +1,22 @@ +--- +layout: multipage-overview +title: Коллекции в Scala +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: На этой странице представлено введение в общие классы коллекций и их методы в Scala 3. +language: ru +num: 36 +previous-page: packaging-imports +next-page: collections-classes +--- + +В этой главе представлены наиболее распространенные коллекции Scala 3 и сопутствующие им методы. +Scala поставляется с множеством типов коллекций, +Вы можете многого добиться, начав использовать лишь небольшое количество типов, а затем, по мере необходимости, начать применять остальные. +Точно так же у каждого типа есть десятки методов, +облегчающих разработку, но можно многого добиться, начав лишь с нескольких. + +Поэтому в этом разделе представлены наиболее распространенные типы и методы коллекций, +которые вам понадобятся для начала работы. diff --git a/_ru/scala3/book/collections-methods.md b/_ru/scala3/book/collections-methods.md new file mode 100644 index 0000000000..503b52d705 --- /dev/null +++ b/_ru/scala3/book/collections-methods.md @@ -0,0 +1,662 @@ +--- +layout: multipage-overview +title: Методы в коллекциях +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице показаны общие методы классов коллекций Scala 3. +language: ru +num: 38 +previous-page: collections-classes +next-page: collections-summary +--- + + + +Важным преимуществом коллекций Scala является то, что они поставляются с десятками методов “из коробки”, +которые доступны как для неизменяемых, так и для изменяемых типов коллекций. +Больше нет необходимости писать пользовательские циклы `for` каждый раз, когда нужно работать с коллекцией. +При переходе от одного проекта к другому, можно обнаружить, что используются одни и те же методы. + +В коллекциях доступны _десятки_ методов, поэтому здесь показаны не все из них. +Показаны только некоторые из наиболее часто используемых методов, в том числе: + +- `map` +- `filter` +- `foreach` +- `head` +- `tail` +- `take`, `takeWhile` +- `drop`, `dropWhile` +- `reduce` + +Следующие методы работают со всеми типами последовательностей, включая `List`, `Vector`, `ArrayBuffer` и т.д. +Примеры рассмотрены на `List`-е, если не указано иное. + +> Важно напомнить, что ни один из методов в `List` не изменяет список. +> Все они работают в функциональном стиле, то есть возвращают новую коллекцию с измененными результатами. + +## Примеры распространенных методов + +Для общего представления в примерах ниже показаны некоторые из наиболее часто используемых методов коллекций. +Вот несколько методов, которые не используют лямбда-выражения: + +{% tabs common-method-examples %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10) + +a.distinct // List(10, 20, 30, 40) +a.drop(2) // List(30, 40, 10) +a.dropRight(2) // List(10, 20, 30) +a.head // 10 +a.headOption // Some(10) +a.init // List(10, 20, 30, 40) +a.intersect(List(19,20,21)) // List(20) +a.last // 10 +a.lastOption // Some(10) +a.slice(2,4) // List(30, 40) +a.tail // List(20, 30, 40, 10) +a.take(3) // List(10, 20, 30) +a.takeRight(2) // List(40, 10) +``` +{% endtab %} + +{% endtabs %} + + +### Функции высшего порядка и лямбда-выражения + +Далее будут показаны некоторые часто используемые функции высшего порядка (HOF), +которые принимают лямбды (анонимные функции). +Для начала приведем несколько вариантов лямбда-синтаксиса, +начиная с самой длинной формы, поэтапно переходящей к наиболее сжатой: + +{% tabs higher-order-functions-example %} + +{% tab 'Scala 2 и 3' %} +```scala +// все эти функции одинаковые и возвращают +// одно и тоже: List(10, 20, 10) + +a.filter((i: Int) => i < 25) // 1. наиболее расширенная форма +a.filter((i) => i < 25) // 2. `Int` необязателен +a.filter(i => i < 25) // 3. скобки можно опустить +a.filter(_ < 25) // 4. `i` необязателен +``` +{% endtab %} + +{% endtabs %} + +В этих примерах: + +1. Первый пример показывает самую длинную форму. + Такое многословие требуется _редко_, только в самых сложных случаях. +2. Компилятор знает, что `a` содержит `Int`, поэтому нет необходимости повторять это в функции. +3. Если в функции только один параметр, например `i`, то скобки не нужны. +4. В случае одного параметра, если он появляется в анонимной функции только раз, его можно заменить на `_`. + +В главе [Анонимные функции][lambdas] представлена более подробная информация +и примеры правил, связанных с сокращением лямбда-выражений. + +Примеры других HOF, использующих краткий лямбда-синтаксис: + +{% tabs anonymous-functions-example %} + +{% tab 'Scala 2 и 3' %} +```scala +a.dropWhile(_ < 25) // List(30, 40, 10) +a.filter(_ > 100) // List() +a.filterNot(_ < 25) // List(30, 40) +a.find(_ > 20) // Some(30) +a.takeWhile(_ < 30) // List(10, 20) +``` +{% endtab %} + +{% endtabs %} + +Важно отметить, что HOF также принимают в качестве параметров методы и функции, а не только лямбда-выражения. +Вот несколько примеров, в которых используется метод с именем `double`. +Снова показаны несколько вариантов лямбда-выражений: + +{% tabs method-as-parameter-example %} + +{% tab 'Scala 2 и 3' %} +```scala +def double(i: Int) = i * 2 + +// these all return `List(20, 40, 60, 80, 20)` +a.map(i => double(i)) +a.map(double(_)) +a.map(double) +``` +{% endtab %} + +{% endtabs %} + +В последнем примере, когда анонимная функция состоит из одного вызова функции, принимающей один аргумент, +нет необходимости указывать имя аргумента, поэтому даже `_` не требуется. + +Наконец, HOF можно комбинировать: + +{% tabs higher-order-functions-combination-example %} + +{% tab 'Scala 2 и 3' %} +```scala +// выдает `List(100, 200)` +a.filter(_ < 40) + .takeWhile(_ < 30) + .map(_ * 10) +``` +{% endtab %} + +{% endtabs %} + + +## Пример данных + +В следующих разделах используются такие списки: + +{% tabs sample-data %} + +{% tab 'Scala 2 и 3' %} +```scala +val oneToTen = (1 to 10).toList +val names = List("adam", "brandy", "chris", "david") +``` +{% endtab %} + +{% endtabs %} + + +## `map` + +Метод `map` проходит через каждый элемент в списке, применяя переданную функцию к элементу, по одному за раз; +затем возвращается новый список с измененными элементами. + +Вот пример применения метода `map` к списку `oneToTen`: + +{% tabs map-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val doubles = oneToTen.map(_ * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} + +{% endtabs %} + +Также можно писать анонимные функции, используя более длинную форму, например: + +{% tabs map-example-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val doubles = oneToTen.map(i => i * 2) +doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20) +``` +{% endtab %} + +{% endtabs %} + +Однако в этом документе будет всегда использоваться первая, более короткая форма. + +Вот еще несколько примеров применения метода `map` к `oneToTen` и `names`: + +{% tabs few-more-examples %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val capNames = names.map(_.capitalize) +capNames: List[String] = List(Adam, Brandy, Chris, David) + +scala> val nameLengthsMap = names.map(s => (s, s.length)).toMap +nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david -> 5) + +scala> val isLessThanFive = oneToTen.map(_ < 5) +isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) +``` +{% endtab %} + +{% endtabs %} + +Как показано в последних двух примерах, совершенно законно (и распространено) использование `map` для возврата коллекции, +которая имеет тип, отличный от исходного типа. + + +## `filter` + +Метод `filter` создает новый список, содержащий только те элементы, которые удовлетворяют предоставленному предикату. +Предикат или условие — это функция, которая возвращает `Boolean` (`true` или `false`). +Вот несколько примеров: + +{% tabs filter-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val lessThanFive = oneToTen.filter(_ < 5) +lessThanFive: List[Int] = List(1, 2, 3, 4) + +scala> val evens = oneToTen.filter(_ % 2 == 0) +evens: List[Int] = List(2, 4, 6, 8, 10) + +scala> val shortNames = names.filter(_.length <= 4) +shortNames: List[String] = List(adam) +``` +{% endtab %} + +{% endtabs %} + +Отличительной особенностью функциональных методов коллекций является то, +что их можно объединять вместе для решения задач. +Например, в этом примере показано, как связать `filter` и `map`: + +{% tabs filter-example-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.filter(_ < 4).map(_ * 10) +``` +{% endtab %} + +{% endtabs %} + +REPL показывает результат: + +{% tabs filter-example-anonymous-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> oneToTen.filter(_ < 4).map(_ * 10) +val res1: List[Int] = List(10, 20, 30) +``` +{% endtab %} + +{% endtabs %} + + +## `foreach` + +Метод `foreach` используется для перебора всех элементов коллекции. +Стоит обратить внимание, что `foreach` используется для побочных эффектов, таких как печать информации. +Вот пример с `names`: + +{% tabs foreach-example %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> names.foreach(println) +adam +brandy +chris +david +``` +{% endtab %} + +{% endtabs %} + + + +## `head` + +Метод `head` взят из Lisp и других более ранних языков функционального программирования. +Он используется для доступа к первому элементу (головному (_head_) элементу) списка: + +{% tabs head-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.head // 1 +names.head // adam +``` +{% endtab %} + +{% endtabs %} + +`String` можно рассматривать как последовательность символов, т.е. строка также является коллекцией, +а значит содержит соответствующие методы. +Вот как `head` работает со строками: + +{% tabs string-head-example %} + +{% tab 'Scala 2 и 3' %} +```scala +"foo".head // 'f' +"bar".head // 'b' +``` +{% endtab %} + +{% endtabs %} + +`head` — отличный метод для работы, но в качестве предостережения следует помнить, что +он также может генерировать исключение при вызове для пустой коллекции: + +{% tabs head-error-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val emptyList = List[Int]() // emptyList: List[Int] = List() +emptyList.head // java.util.NoSuchElementException: head of empty list +``` +{% endtab %} + +{% endtabs %} + +Чтобы не натыкаться на исключение вместо `head` желательно использовать `headOption`, +особенно при разработке в функциональном стиле: + +{% tabs head-option-example %} + +{% tab 'Scala 2 и 3' %} +```scala +emptyList.headOption // None +``` +{% endtab %} + +{% endtabs %} + +`headOption` не генерирует исключение, а возвращает тип `Option` со значением `None`. +Более подробно о функциональном стиле программирования будет рассказано [в соответствующей главе][fp-intro]. + + +## `tail` + +Метод `tail` также взят из Lisp и используется для вывода всех элементов в списке после `head`. + +{% tabs tail-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.head // 1 +oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10) + +names.head // adam +names.tail // List(brandy, chris, david) +``` +{% endtab %} + +{% endtabs %} + +Так же, как и `head`, `tail` можно использовать со строками: + +{% tabs string-tail-example %} + +{% tab 'Scala 2 и 3' %} +```scala +"foo".tail // "oo" +"bar".tail // "ar" +``` +{% endtab %} + +{% endtabs %} + +`tail` выбрасывает исключение _java.lang.UnsupportedOperationException_, если список пуст, +поэтому, как и в случае с `head` и `headOption`, существует также метод `tailOption`, +который предпочтительнее в функциональном программировании. + +Список матчится, поэтому можно использовать такие выражения: + +{% tabs tail-match-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val x :: xs = names +``` +{% endtab %} + +{% endtabs %} + +Помещение этого кода в REPL показывает, что `x` назначается заглавному элементу списка, а `xs` назначается "хвосту": + +{% tabs tail-match-example-repl %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> val x :: xs = names +val x: String = adam +val xs: List[String] = List(brandy, chris, david) +``` +{% endtab %} + +{% endtabs %} + +Подобное сопоставление с образцом полезно во многих случаях, например, при написании метода `sum` с использованием рекурсии: + +{% tabs tail-match-sum-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(list: List[Int]): Int = list match { + case Nil => 0 + case x :: xs => x + sum(xs) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(list: List[Int]): Int = list match + case Nil => 0 + case x :: xs => x + sum(xs) +``` +{% endtab %} + +{% endtabs %} + + + +## `take`, `takeRight`, `takeWhile` + +Методы `take`, `takeRight` и `takeWhile` предоставляют удобный способ “брать” (_taking_) элементы из списка для создания нового. +Примеры `take` и `takeRight`: + +{% tabs take-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.take(1) // List(1) +oneToTen.take(2) // List(1, 2) + +oneToTen.takeRight(1) // List(10) +oneToTen.takeRight(2) // List(9, 10) +``` +{% endtab %} + +{% endtabs %} + +Обратите внимание, как эти методы работают с «пограничными» случаями, +когда запрашивается больше элементов, чем есть в последовательности, +или запрашивается ноль элементов: + +{% tabs take-edge-cases-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.take(0) // List() +oneToTen.takeRight(0) // List() +``` +{% endtab %} + +{% endtabs %} + +А это `takeWhile`, который работает с функцией-предикатом: + +{% tabs take-while-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4) +names.takeWhile(_.length < 5) // List(adam) +``` +{% endtab %} + +{% endtabs %} + + +## `drop`, `dropRight`, `dropWhile` + +`drop`, `dropRight` и `dropWhile` удаляют элементы из списка +и, по сути, противоположны своим аналогам “take”. +Вот некоторые примеры: + +{% tabs drop-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.drop(5) // List(6, 7, 8, 9, 10) + +oneToTen.dropRight(8) // List(1, 2) +oneToTen.dropRight(7) // List(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +Пограничные случаи: + +{% tabs drop-edge-cases-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.drop(Int.MaxValue) // List() +oneToTen.dropRight(Int.MaxValue) // List() +oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +``` +{% endtab %} + +{% endtabs %} + +А это `dropWhile`, который работает с функцией-предикатом: + +{% tabs drop-while-example %} + +{% tab 'Scala 2 и 3' %} +```scala +oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10) +names.dropWhile(_ != "chris") // List(chris, david) +``` +{% endtab %} + +{% endtabs %} + + +## `reduce` + +Метод `reduce` позволяет свертывать коллекцию до одного агрегируемого значения. +Он принимает функцию (или анонимную функцию) и последовательно применяет эту функцию к элементам в списке. + +Лучший способ объяснить `reduce` — создать небольшой вспомогательный метод. +Например, метод `add`, который складывает вместе два целых числа, +а также предоставляет хороший вывод отладочной информации: + +{% tabs reduce-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def add(x: Int, y: Int): Int = { + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def add(x: Int, y: Int): Int = + val theSum = x + y + println(s"received $x and $y, their sum is $theSum") + theSum +``` +{% endtab %} + +{% endtabs %} + +Рассмотрим список: + +{% tabs reduce-example-init %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List(1,2,3,4) +``` +{% endtab %} + +{% endtabs %} + +вот что происходит, когда в `reduce` передается метод `add`: + +{% tabs reduce-example-evaluation %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(add) +received 1 and 2, their sum is 3 +received 3 and 3, their sum is 6 +received 6 and 4, their sum is 10 +res0: Int = 10 +``` +{% endtab %} + +{% endtabs %} + +Как видно из результата, функция `reduce` использует `add` для сокращения списка `a` до единственного значения, +в данном случае — суммы всех чисел в списке. + +`reduce` можно использовать с анонимными функциями: + +{% tabs reduce-example-sum %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(_ + _) +res0: Int = 10 +``` +{% endtab %} + +{% endtabs %} + +Аналогично можно использовать другие функции, например, умножение: + +{% tabs reduce-example-multiply %} + +{% tab 'Scala 2 и 3' %} +```scala +scala> a.reduce(_ * _) +res1: Int = 24 +``` +{% endtab %} + +{% endtabs %} + +> Важная концепция, которую следует знать о `reduce`, заключается в том, что, как следует из ее названия +> (_reduce_ - сокращать), она используется для сокращения коллекции до одного значения. + + +## Дальнейшее изучение коллекций + +В коллекциях Scala есть десятки дополнительных методов, которые избавляют от необходимости писать еще один цикл `for`. +Более подробную информацию о коллекциях Scala см. +в разделе [Изменяемые и неизменяемые коллекции][mut-immut-colls] +и [Архитектура коллекций Scala][architecture]. + +> В качестве последнего примечания, при использовании Java-кода в проекте Scala, +> коллекции Java можно преобразовать в коллекции Scala. +> После этого, их можно использовать в выражениях `for`, +> а также воспользоваться преимуществами методов функциональных коллекций Scala. +> Более подробную информацию можно найти в разделе [Взаимодействие с Java][interacting]. + + +[interacting]: {% link _overviews/scala3-book/interacting-with-java.md %} +[lambdas]: {% link _overviews/scala3-book/fun-anonymous-functions.md %} +[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %} +[mut-immut-colls]: {% link _overviews/collections-2.13/overview.md %} +[architecture]: {% link _overviews/core/architecture-of-scala-213-collections.md %} + diff --git a/_ru/scala3/book/collections-summary.md b/_ru/scala3/book/collections-summary.md new file mode 100644 index 0000000000..6e852ad359 --- /dev/null +++ b/_ru/scala3/book/collections-summary.md @@ -0,0 +1,35 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: На этой странице представлен краткий итог главы «Коллекции». +language: ru +num: 39 +previous-page: collections-methods +next-page: fp-intro +--- + +В этой главе представлен обзор общих коллекций Scala 3 и сопутствующих им методов. +Как было показано, Scala поставляется с множеством коллекций и методов. + +Если вам нужно увидеть более подробную информацию о типах коллекций, +показанных в этой главе, см. их Scaladoc страницы: + +- [List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) +- [Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) +- [ArrayBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ArrayBuffer.html) +- [Range](https://www.scala-lang.org/api/current/scala/collection/immutable/Range.html) + +Также упоминавшиеся неизменяемые `Map` и `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) + +и изменяемые `Map` и `Set`: + +- [Map](https://www.scala-lang.org/api/current/scala/collection/mutable/Map.html) +- [Set](https://www.scala-lang.org/api/current/scala/collection/mutable/Set.html) + diff --git a/_ru/scala3/book/control-structures.md b/_ru/scala3/book/control-structures.md index b72b476278..37e78c1633 100644 --- a/_ru/scala3/book/control-structures.md +++ b/_ru/scala3/book/control-structures.md @@ -450,14 +450,14 @@ AR: Arizona {% endtab %} {% endtabs %} -Когда цикл `for` перебирает карту, каждая пара ключ/значение привязывается +Когда цикл `for` перебирает мапу, каждая пара ключ/значение привязывается к переменным `abbrev` и `fullName`, которые находятся в кортеже: ```scala (abbrev, fullName) <- states ``` -По мере выполнения цикла переменная `abbrev` принимает значение текущего _ключа_ в карте, +По мере выполнения цикла переменная `abbrev` принимает значение текущего _ключа_ в мапе, а переменная `fullName` - соответствующему ключу _значению_. ## Выражение `for` diff --git a/_ru/scala3/book/fp-functional-error-handling.md b/_ru/scala3/book/fp-functional-error-handling.md new file mode 100644 index 0000000000..bf6d299fbd --- /dev/null +++ b/_ru/scala3/book/fp-functional-error-handling.md @@ -0,0 +1,436 @@ +--- +layout: multipage-overview +title: Функциональная обработка ошибок +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе представлено введение в функциональную обработку ошибок в Scala 3. +language: ru +num: 45 +previous-page: fp-functions-are-values +next-page: fp-summary +--- + + + +Функциональное программирование похоже на написание ряда алгебраических уравнений, +и поскольку алгебра не имеет null значений или исключений, они не используются и в ФП. +Что поднимает интересный вопрос: как быть в ситуациях, в которых вы обычно используете null значение или исключение программируя в ООП стиле? + +Решение Scala заключается в использовании конструкций, основанных на классах типа `Option`/`Some`/`None`. +Этот урок представляет собой введение в использование такого подхода. + +Примечание: + +- классы `Some` и `None` являются подклассами `Option` +- вместо того чтобы многократно повторять “`Option`/`Some`/`None`”, + следующий текст обычно просто ссылается на “`Option`” или на “классы `Option`” + + +## Первый пример + +Хотя этот первый пример не имеет дело с `null` значениями, это хороший способ познакомиться с классами `Option`. + +Представим, что нужно написать метод, который упрощает преобразование строк в целочисленные значения. +И нужен элегантный способ обработки исключения, которое возникает, +когда метод получает строку типа `"Hello"` вместо `"1"`. +Первое предположение о таком методе может выглядеть следующим образом: + +{% tabs fp-java-try class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Int = + try { + Integer.parseInt(s.trim) + } catch { + case e: Exception => 0 + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Int = + try + Integer.parseInt(s.trim) + catch + case e: Exception => 0 +``` +{% endtab %} + +{% endtabs %} + +Если преобразование работает, метод возвращает правильное значение `Int`, но в случае сбоя метод возвращает `0`. +Для некоторых целей это может быть хорошо, но не совсем точно. +Например, метод мог получить `"0"`, но мог также получить `"foo"`, `"bar"` +или бесконечное количество других строк, которые выдадут исключение. +Это реальная проблема: как определить, когда метод действительно получил `"0"`, а когда получил что-то еще? +При таком подходе нет способа узнать правильный ответ наверняка. + + +## Использование Option/Some/None + +Распространенным решением этой проблемы в Scala является использование классов, +известных как `Option`, `Some` и `None`. +Классы `Some` и `None` являются подклассами `Option`, поэтому решение работает следующим образом: + +- объявляется, что `makeInt` возвращает тип `Option` +- если `makeInt` получает строку, которую он _может_ преобразовать в `Int`, ответ помещается внутрь `Some` +- если `makeInt` получает строку, которую _не может_ преобразовать, то возвращает `None` + +Вот доработанная версия `makeInt`: + +{% tabs fp--try-option class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def makeInt(s: String): Option[Int] = + try { + Some(Integer.parseInt(s.trim)) + } catch { + case e: Exception => None + } +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def makeInt(s: String): Option[Int] = + try + Some(Integer.parseInt(s.trim)) + catch + case e: Exception => None +``` +{% endtab %} + +{% endtabs %} + +Этот код можно прочитать следующим образом: +“Когда данная строка преобразуется в целое число, верните значение `Int`, заключенное в `Some`, например `Some(1)`. +Когда строка не может быть преобразована в целое число и генерируется исключение, метод возвращает значение `None`.” + +Эти примеры показывают, как работает `makeInt`: + +{% tabs fp-try-option-example %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = makeInt("1") // Some(1) +val b = makeInt("one") // None +``` +{% endtab %} + +{% endtabs %} + +Как показано, строка `"1"` приводится к `Some(1)`, а строка `"one"` - к `None`. +В этом суть альтернативного подхода к обработке ошибок. +Данная техника используется для того, чтобы методы могли возвращать _значения_ вместо _исключений_. +В других ситуациях значения `Option` также используются для замены `null` значений. + +Примечание: + +- этот подход используется во всех классах библиотеки Scala, а также в сторонних библиотеках Scala. +- ключевым моментом примера является то, что функциональные методы не генерируют исключения; + вместо этого они возвращают такие значения, как `Option`. + + +## Потребитель makeInt + +Теперь представим, что мы являемся потребителем метода `makeInt`. +Известно, что он возвращает подкласс `Option[Int]`, поэтому возникает вопрос: +как работать с такими возвращаемыми типами? + +Есть два распространенных ответа, в зависимости от потребностей: + +- использование `match` выражений +- использование `for` выражений + +## Использование `match` выражений + +Одним из возможных решений является использование выражения `match`: + +{% tabs fp-option-match class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +makeInt(x) match { + case Some(i) => println(i) + case None => println("That didn’t work.") +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +makeInt(x) match + case Some(i) => println(i) + case None => println("That didn’t work.") +``` +{% endtab %} + +{% endtabs %} + +В этом примере, если `x` можно преобразовать в `Int`, вычисляется первый вариант в правой части предложения `case`; +если `x` не может быть преобразован в `Int`, вычисляется второй вариант в правой части предложения `case`. + + +## Использование `for` выражений + +Другим распространенным решением является использование выражения `for`, то есть комбинации `for`/`yield`. +Например, представим, что необходимо преобразовать три строки в целочисленные значения, а затем сложить их. +Решение задачи с использованием выражения `for`: + +{% tabs fp-for-comprehension class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} + +{% endtabs %} + +После выполнения этого выражения `y` может принять одно из двух значений: + +- если _все_ три строки конвертируются в значения `Int`, `y` будет равно `Some[Int]`, т.е. целым числом, обернутым внутри `Some` +- если _какая-либо_ из трех строк не может быть преобразована в `Int`, `y` равен `None` + +Это можно проверить на примере: + +{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for { + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +} yield { + a + b + c +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val stringA = "1" +val stringB = "2" +val stringC = "3" + +val y = for + a <- makeInt(stringA) + b <- makeInt(stringB) + c <- makeInt(stringC) +yield + a + b + c +``` +{% endtab %} + +{% endtabs %} + +С этими демонстрационными данными переменная `y` примет значение `Some(6)`. + +Чтобы увидеть негативный кейс, достаточно изменить любую из строк на что-то, что нельзя преобразовать в целое число. +В этом случае `y` равно `None`: + +{% tabs fp-for-comprehension-failure-result %} + +{% tab 'Scala 2 и 3' %} +```scala +y: Option[Int] = None +``` +{% endtab %} + +{% endtabs %} + + +## Восприятие Option, как контейнера + +Для лучшего восприятия `Option`, его можно представить как _контейнер_: + +- `Some` представляет собой контейнер с одним элементом +- `None` не является контейнером, в нем ничего нет + +Если предпочтительнее думать об `Option` как о ящике, то `None` подобен пустому ящику. +Что-то в нём могло быть, но нет. + + +## Использование `Option` для замены `null` + +Возвращаясь к значениям `null`, место, где `null` значение может незаметно проникнуть в код, — класс, подобный этому: + +{% tabs fp=case-class-nulls %} + +{% tab 'Scala 2 и 3' %} +```scala +class Address( + var street1: String, + var street2: String, + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} + +{% endtabs %} + +Хотя каждый адрес имеет значение `street1`, значение `street2` не является обязательным. +В результате полю `street2` можно присвоить значение `null`: + +{% tabs fp-case-class-nulls-example class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + null, // <-- О! Значение null! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + null, // <-- О! Значение null! + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% endtabs %} + +Исторически сложилось так, что в этой ситуации разработчики использовали пустые строки и значения `null`, +оба варианта это “костыль” для решения основной проблемы: `street2` - _необязательное_ поле. +В Scala и других современных языках правильное решение состоит в том, +чтобы заранее объявить, что `street2` является необязательным: + + +{% tabs fp-case-class-with-options %} + +{% tab 'Scala 2 и 3' %} +```scala +class Address( + var street1: String, + var street2: Option[String], // необязательное значение + var city: String, + var state: String, + var zip: String +) +``` +{% endtab %} + +{% endtabs %} + +Теперь можно написать более точный код: + +{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "1 Main Street", + None, // 'street2' не имеет значения + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "1 Main Street", + None, // 'street2' не имеет значения + "North Pole", + "Alaska", + "99705" +) +``` +{% endtab %} + +{% endtabs %} + +или так: + +{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +val santa = new Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +val santa = Address( + "123 Main Street", + Some("Apt. 2B"), + "Talkeetna", + "Alaska", + "99676" +) +``` +{% endtab %} + +{% endtabs %} + + + +## `Option` — не единственное решение + +В этом разделе основное внимание уделялось `Option` классам, но у Scala есть несколько других альтернатив. + +Например, три класса, известные как `Try`/`Success`/`Failure`, работают также, +но (а) эти классы в основном используются, когда код может генерировать исключения, +и (б) когда желательно использовать класс `Failure`, потому что он дает доступ к сообщению об исключении. +Например, классы `Try` обычно используются при написании методов, которые взаимодействуют с файлами, +базами данных или интернет-службами, поскольку эти функции могут легко создавать исключения. + + +## Краткое ревью + +Этот раздел был довольно большим, поэтому давайте подведем краткое ревью: + +- функциональные программисты не используют `null` значения +- основной заменой `null` значениям является использование классов `Option` +- функциональные методы не выдают исключений; вместо этого они возвращают такие значения, как `Option`, `Try` или `Either` +- распространенными способами работы со значениями `Option` являются выражения `match` и `for` +- `Option` можно рассматривать как контейнеры с одним элементом (`Some`) и без элементов (`None`) +- `Option` также можно использовать для необязательных параметров конструктора или метода diff --git a/_ru/scala3/book/fp-functions-are-values.md b/_ru/scala3/book/fp-functions-are-values.md new file mode 100644 index 0000000000..62cacc4540 --- /dev/null +++ b/_ru/scala3/book/fp-functions-are-values.md @@ -0,0 +1,161 @@ +--- +layout: multipage-overview +title: Функции — это значения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование функций в качестве значений в функциональном программировании. +language: ru +num: 44 +previous-page: fp-pure-functions +next-page: fp-functional-error-handling +--- + + +Хотя каждый когда-либо созданный язык программирования, вероятно, позволяет писать чистые функции, +вторая важная особенность ФП на Scala заключается в том, что _функции можно создавать как значения_, +точно так же, как создаются значения `String` и `Int`. + +Эта особенность даёт много преимуществ, опишем наиболее распространенные из них: +(a) можно определять методы, принимающие в качестве параметров функции +и (b) можно передавать функции в качестве параметров в методы. + +Такой подход можно было наблюдать в предыдущих главах, когда демонстрировались такие методы, как `map` и `filter`: + +{% tabs fp-function-as-values-anonymous %} + +{% tab 'Scala 2 и 3' %} +```scala +val nums = (1 to 10).toList + +val doubles = nums.map(_ * 2) // удваивает каждое значение +val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4) +``` +{% endtab %} + +{% endtabs %} + +В этих примерах анонимные функции передаются в `map` и `filter`. + +> Анонимные функции также известны как _лямбды_ (_lambdas_). + +Помимо передачи анонимных функций в `filter` и `map`, в них также можно передать _методы_: + +{% tabs fp-function-as-values-defined %} + +{% tab 'Scala 2 и 3' %} +```scala +// два метода +def double(i: Int): Int = i * 2 +def underFive(i: Int): Boolean = i < 5 + +// передача этих методов в filter и map +val doubles = nums.filter(underFive).map(double) +``` +{% endtab %} + +{% endtabs %} + +Возможность обращаться с методами и функциями как со значениями — мощное свойство, +предоставляемое языками функционального программирования. + +> Технически функция, которая принимает другую функцию в качестве входного параметра, известна как _функция высшего порядка_. +> (Если вам нравится юмор, как кто-то однажды написал, это все равно, что сказать, +> что класс, который принимает экземпляр другого класса в качестве параметра конструктора, +> является классом высшего порядка.) + + +## Функции, анонимные функции и методы + +В примерах выше анонимная функция это: + +{% tabs fp-anonymous-function-short %} + +{% tab 'Scala 2 и 3' %} +```scala +_ * 2 +``` +{% endtab %} + +{% endtabs %} + +Как было показано в обсуждении [функций высшего порядка][hofs], `_ * 2` - сокращенная версия синтаксиса: + +{% tabs fp-anonymous-function-full %} + +{% tab 'Scala 2 и 3' %} +```scala +(i: Int) => i * 2 +``` +{% endtab %} + +{% endtabs %} + +Такие функции называются “анонимными”, потому что им не присваивается определенное имя. +Для того чтобы это имя задать, достаточно просто присвоить его переменной: + +{% tabs fp-function-assignement %} + +{% tab 'Scala 2 и 3' %} +```scala +val double = (i: Int) => i * 2 +``` +{% endtab %} + +{% endtabs %} + +Теперь появилась именованная функция, назначенная переменной `double`. +Можно использовать эту функцию так же, как используется метод: + +{% tabs fp-function-used-like-method %} + +{% tab 'Scala 2 и 3' %} +```scala +double(2) // 4 +``` +{% endtab %} + +{% endtabs %} + +В большинстве случаев не имеет значения, является ли `double` функцией или методом; +Scala позволяет обращаться с ними одинаково. +За кулисами технология Scala, которая позволяет обращаться с методами так же, +как с функциями, известна как [Eta Expansion][eta]. + +Эта способность беспрепятственно передавать функции в качестве переменных +является отличительной чертой функциональных языков программирования, таких как Scala. +И, как было видно на примерах `map` и `filter`, +возможность передавать функции в другие функции помогает создавать код, +который является кратким и при этом читабельным — _выразительным_. + +Вот еще несколько примеров: + +{% tabs fp-function-as-values-example %} + +{% tab 'Scala 2 и 3' %} +```scala +List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE) +List("bob", "joe").map(_.capitalize) // List(Bob, Joe) +List("plum", "banana").map(_.length) // List(4, 6) + +val fruits = List("apple", "pear") +fruits.map(_.toUpperCase) // List(APPLE, PEAR) +fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R) + +val nums = List(5, 1, 3, 11, 7) +nums.map(_ * 2) // List(10, 2, 6, 22, 14) +nums.filter(_ > 3) // List(5, 11, 7) +nums.takeWhile(_ < 6) // List(5, 1, 3) +nums.sortWith(_ < _) // List(1, 3, 5, 7, 11) +nums.sortWith(_ > _) // List(11, 7, 5, 3, 1) + +nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5) +``` +{% endtab %} + +{% endtabs %} + + +[hofs]: {% link _overviews/scala3-book/fun-hofs.md %} +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fp-immutable-values.md b/_ru/scala3/book/fp-immutable-values.md new file mode 100644 index 0000000000..24ffaf8613 --- /dev/null +++ b/_ru/scala3/book/fp-immutable-values.md @@ -0,0 +1,109 @@ +--- +layout: multipage-overview +title: Неизменяемые значения +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование неизменяемых значений в функциональном программировании. +language: ru +num: 42 +previous-page: fp-what-is-fp +next-page: fp-pure-functions +--- + +В чистом функциональном программировании используются только неизменяемые значения. +В Scala это означает: + +- все переменные создаются как поля `val` +- используются только неизменяемые классы коллекций, такие как `List`, `Vector` и неизменяемые классы `Map` и `Set` + +Использование только неизменяемых переменных поднимает интересный вопрос: если все статично, как вообще что-то меняется? + +Когда дело доходит до использования коллекций, один из ответов заключается в том, +что существующая коллекция не меняется; вместо этого функция применяется к коллекции, чтобы создать новую. +Именно здесь вступают в действие функции высшего порядка, такие как `map` и `filter`. + +Например, представим, что есть список имен в нижнем регистре — `List[String]`, +и необходимо найти все имена, начинающиеся с буквы `"j"`, чтобы затем сделать первые буквы заглавными. +В ФП код будет выглядеть так: + +{% tabs fp-list %} + +{% tab 'Scala 2 и 3' %} +```scala +val a = List("jane", "jon", "mary", "joe") +val b = a.filter(_.startsWith("j")) + .map(_.capitalize) +``` +{% endtab %} + +{% endtabs %} + +Как показано, исходный список `a` не меняется. +Вместо этого к `a` применяется функция фильтрации и преобразования, чтобы создать новую коллекцию, +и результат присваивается неизменяемой переменной `b`. + +Точно так же в ФП не используются классы с изменяемыми параметрами конструктора `var`. +В ФП создание такого класса не привествуется: + +{% tabs fp--class-variables %} + +{% tab 'Scala 2 и 3' %} +```scala +// не стоит этого делать в ФП +class Person(var firstName: String, var lastName: String) + --- --- +``` +{% endtab %} + +{% endtabs %} + +Вместо этого обычно создаются `case` классы, чьи параметры конструктора по умолчанию неизменяемые (`val`): + +{% tabs fp-immutable-case-class %} + +{% tab 'Scala 2 и 3' %} +```scala +case class Person(firstName: String, lastName: String) +``` +{% endtab %} + +{% endtabs %} + +Теперь можно создать экземпляр `Person` как поле `val`: + +{% tabs fp-case-class-creation %} + +{% tab 'Scala 2 и 3' %} +```scala +val reginald = Person("Reginald", "Dwight") +``` +{% endtab %} + +{% endtabs %} + +Затем, при необходимости внести изменения в данные, используется метод `copy`, +который поставляется с `case` классом, чтобы “обновлять данные через создание копии”, +например так: + +{% tabs fp-case-class-copy %} + +{% tab 'Scala 2 и 3' %} +```scala +val elton = reginald.copy( + firstName = "Elton", // обновить имя + lastName = "John" // обновить фамилию +) +``` +{% endtab %} + +{% endtabs %} + +Существуют множество других приёмов работы с неизменяемыми коллекциями и переменными. + +> В зависимости от задач вместо `case` классов можно создавать перечисления, trait-ы или классы. +> Для более подробной информации см. главу [“Моделирование данных”][modeling]. + + +[modeling]: {% link _overviews/scala3-book/domain-modeling-intro.md %} diff --git a/_ru/scala3/book/fp-intro.md b/_ru/scala3/book/fp-intro.md new file mode 100644 index 0000000000..7961b13a83 --- /dev/null +++ b/_ru/scala3/book/fp-intro.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: Функциональное программирование +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: В этой главе представлено введение в функциональное программирование в Scala 3. +language: ru +num: 40 +previous-page: collections-summary +next-page: fp-what-is-fp +--- + + +Scala позволяет писать код в стиле объектно-ориентированного программирования (ООП), +в стиле функционального программирования (ФП), а также в гибридном стиле, используя оба подхода в комбинации. +По словам [Martin Odersky](https://twitter.com/alexelcu/status/996408359514525696), +сущность Scala — это слияние функционального и объектно-ориентированного программирования в типизированной среде: + +- Функции для логики +- Объекты для модульности + +В этой главе предполагается, что вы знакомы с ООП и менее знакомы с ФП, +поэтому в ней представлено краткое введение в несколько основных концепций функционального программирования: + +- Что такое функциональное программирование? +- Неизменяемые значения +- Чистые функции +- Функции — это значения +- Функциональная обработка ошибок diff --git a/_ru/scala3/book/fp-pure-functions.md b/_ru/scala3/book/fp-pure-functions.md new file mode 100644 index 0000000000..fd70cc5694 --- /dev/null +++ b/_ru/scala3/book/fp-pure-functions.md @@ -0,0 +1,153 @@ +--- +layout: multipage-overview +title: Чистые функции +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: В этом разделе рассматривается использование чистых функций в функциональном программировании. +language: ru +num: 43 +previous-page: fp-immutable-values +next-page: fp-functions-are-values +--- + + +Еще одна концепция, которую Scala предлагает для помощи в написании функционального кода, — это возможность писать чистые функции. +_Чистая функция_ (_pure function_) может быть определена следующим образом: + +- функция `f` является чистой, если при одних и тех же входных данных `x` она всегда возвращает один и тот же результат `f(x)` +- результат функции зависит _только_ от входных данных и её реализации +- чистые функции только вычисляют результат, ничего не меняя за пределами этих функций + +Из этого следует: + +- чистая функция не изменяет свои входные параметры +- она не мутирует какое-либо скрытое состояние +- у неё нет “черных ходов”: он не читает данные из внешнего мира (включая консоль, веб-сервисы, базы данных, файлы и т.д.) + и не записывает данные вовне + +В результате этого определения каждый раз, когда вызывается чистая функция с одним и тем же входным значением (значениями), +всегда будет выдаваться один и тот же результат. +Например, можно вызывать функцию `double` бесконечное число раз с входным значением `2`, и всегда получать результат `4`. + + +## Примеры чистых функций + +Учитывая это определение, методы в пакете `scala.math._` являются чистыми функциями: + +- `abs` +- `ceil` +- `max` + +Эти методы `String` также являются чистыми функциями: + +- `isEmpty` +- `length` +- `substring` + +Большинство методов в классах коллекций Scala также работают как чистые функции, +включая `drop`, `filter`, `map` и многие другие. + +> В Scala _функции_ и _методы_ почти полностью взаимозаменяемы, +> поэтому, хотя здесь используется общепринятый отраслевой термин “чистая функция”, +> этот термин можно использовать как для описания функций, так и методов. +> Как методы могут использоваться подобно функциям описано в главе [Eta расширение][eta]. + + +## Примеры “грязных” функций + +И наоборот, следующие функции “_грязные_” (_impure_), потому что они нарушают определение pure function: + +- `println` — методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., “грязные” +- `currentTimeMillis` — все методы, связанные с датой и временем, “грязные”, + потому что их вывод зависит от чего-то другого, кроме входных параметров +- `sys.error` — методы генерации исключений “грязные”, потому что они не “просто возвращают результат” + +“Грязные” функции часто делают одно из следующего: + +- читают из скрытого состояния, т.е. обращаются к параметрам и данным, + не переданным в функцию явным образом в качестве входных параметров +- запись в скрытое состояние +- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе +- выполняют какой-либо ввод-вывод с внешним миром + +> В общем, следует остерегаться функций с возвращаемым типом `Unit`. +> Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, - +> это достижение какого-то побочного эффекта. +> Как следствие, часто использование этих функций является “грязным”. + + +## Но грязные функции все же необходимы ... + +Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, поэтому рекомендуется следующее: + +> Напишите ядро вашего приложения, используя только “чистые” функции, +> а затем напишите “грязную” “оболочку” вокруг этого ядра для взаимодействия с внешним миром. +> Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт. + +Важно отметить, что есть способы сделать “нечистое” взаимодействие с внешним миром более “чистым”. +Например, можно услышать об использовании `IO` монады для обработки ввода-вывода. +Эти темы выходят за рамки данного документа, поэтому для простоты можно думать, +что ФП приложения имеют ядро из “чистых” функций, +которые объединены с другими функциями для взаимодействия с внешним миром. + + +## Написание “чистых” функций + +**Примечание**: в этом разделе для обозначения методов Scala часто используется общепринятый в отрасли термин “чистая функция”. + +Для написания чистых функций на Scala, достаточно писать их, +используя синтаксис методов Scala (хотя также можно использовать и синтаксис функций Scala). +Например, вот чистая функция, которая удваивает заданное ей входное значение: + +{% tabs fp-pure-function %} + +{% tab 'Scala 2 и 3' %} +```scala +def double(i: Int): Int = i * 2 +``` +{% endtab %} + +{% endtabs %} + +Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии: + +{% tabs fp-pure-recursive-function class=tabs-scala-version %} + +{% tab 'Scala 2' %} +```scala +def sum(xs: List[Int]): Int = xs match { + case Nil => 0 + case head :: tail => head + sum(tail) +} +``` +{% endtab %} + +{% tab 'Scala 3' %} +```scala +def sum(xs: List[Int]): Int = xs match + case Nil => 0 + case head :: tail => head + sum(tail) +``` +{% endtab %} + +{% endtabs %} + +Вышеописанные функции соответствуют определению “чистых”. + + +## Ключевые моменты + +Первым ключевым моментом этого раздела является определение чистой функции: + +> _Чистая функция_ — это функция, которая зависит только от своих объявленных входных данных +> и своей реализации для получения результата. +> Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его. + +Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром. +Таким образом, упрощенный способ представления о функциональных программах состоит в том, +что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром. + + +[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %} diff --git a/_ru/scala3/book/fp-summary.md b/_ru/scala3/book/fp-summary.md new file mode 100644 index 0000000000..eea013a88a --- /dev/null +++ b/_ru/scala3/book/fp-summary.md @@ -0,0 +1,31 @@ +--- +layout: multipage-overview +title: Обзор +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Этот раздел суммирует предыдущие разделы функционального программирования. +language: ru +num: 46 +previous-page: fp-functional-error-handling +next-page: +--- + + +В этой главе представлено общее введение в функциональное программирование на Scala. +Охвачены следующие темы: + +- Что такое функциональное программирование? +- Неизменяемые значения +- Чистые функции +- Функции — это значения +- Функциональная обработка ошибок + +Как уже упоминалось, функциональное программирование — обширная тема, +поэтому все, что мы можем сделать в этой книге, — это коснуться перечисленных вводных понятий. +Дополнительные сведения см. [в справочной документации][reference]. + + +[reference]: {{ site.scala3ref }}/overview.html + diff --git a/_ru/scala3/book/fp-what-is-fp.md b/_ru/scala3/book/fp-what-is-fp.md new file mode 100644 index 0000000000..9e3f046542 --- /dev/null +++ b/_ru/scala3/book/fp-what-is-fp.md @@ -0,0 +1,48 @@ +--- +layout: multipage-overview +title: Что такое функциональное программирование? +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: section +description: Этот раздел дает ответ на вопрос, что такое функциональное программирование? +language: ru +num: 41 +previous-page: fp-intro +next-page: fp-immutable-values +--- + + +[Wikipedia](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) +определяет _функциональное программирование_ следующим образом: + +
+

+Функциональное программирование — парадигма программирования, +в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних. +Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных +и результатов других функций, и не предполагает явного хранения состояния программы. +Соответственно, не предполагает оно и изменяемость этого состояния. +

+

 

+

+В функциональном программировании функции рассматриваются как “граждане первого класса”, +что означает, что они могут быть привязаны к именам (включая локальные идентификаторы), +передаваться в качестве аргументов и возвращаться из других функций, как и любой другой тип данных. +Это позволяет писать программы в декларативном и составном стиле, где небольшие функции объединяются модульным образом. +

+
+ +Также полезно знать, что опытные функциональные программисты рассматривают свой код математически, +что объединение чистых функций вместе похоже на объединение ряда алгебраических уравнений. + +Когда пишется функциональный код, вы чувствуете себя математиком, и как только понимаете парадигму, +то хотите писать только чистые функции, которые всегда возвращают _значения_, а не исключения или null, +чтобы можно было комбинировать чистые функции вместе. +Ощущение, что вы пишете математические уравнения (выражения), является движущим желанием, +заставляющим использовать _только_ чистые функции и неизменяемые значения - +это то, что используется в алгебре и других формах математики. + +Функциональное программирование - это большая тема, и нет простого способа сжать её всю в одну главу. +В следующих разделах будет представлен обзор основных тем и показаны некоторые инструменты, +предоставляемые Scala для написания функционального кода. diff --git a/_ru/scala3/book/fun-summary.md b/_ru/scala3/book/fun-summary.md index 497e4d4c5e..77a72a05b2 100644 --- a/_ru/scala3/book/fun-summary.md +++ b/_ru/scala3/book/fun-summary.md @@ -9,7 +9,7 @@ description: На этой странице представлен обзор п language: ru num: 34 previous-page: fun-write-method-returns-function -next-page: +next-page: packaging-imports --- Это была длинная глава, поэтому давайте рассмотрим ключевые моменты, которые мы прошли. diff --git a/_ru/scala3/book/packaging-imports.md b/_ru/scala3/book/packaging-imports.md new file mode 100644 index 0000000000..bde9efdd30 --- /dev/null +++ b/_ru/scala3/book/packaging-imports.md @@ -0,0 +1,418 @@ +--- +layout: multipage-overview +title: Пакеты и импорт +scala3: true +partof: scala3-book +overview-name: "Scala 3 — Book" +type: chapter +description: Обсуждение использования пакетов и импорта для организации кода, создания связанных модулей кода, управления областью действия и предотвращения конфликтов пространств имен. +language: ru +num: 35 +previous-page: fun-summary +next-page: collections-intro +--- + + +Scala использует _packages_ для создания пространств имен, которые позволяют модульно разбивать программы. +Scala поддерживает стиль именования пакетов, используемый в Java, а также нотацию пространства имен “фигурные скобки”, +используемую такими языками, как C++ и C#. + +Подход Scala к импорту похож на Java, но более гибкий. +С помощью Scala можно: + +- импортировать пакеты, классы, объекты, trait-ы и методы +- размещать операторы импорта в любом месте +- скрывать и переименовывать участников при импорте + +Эти особенности демонстрируются в следующих примерах. + + +## Создание пакета + +Пакеты создаются путем объявления одного или нескольких имен пакетов в начале файла Scala. +Например, если ваше доменное имя _acme.com_ и вы работаете с пакетом _model_ приложения с именем _myapp_, +объявление пакета выглядит следующим образом: + +```scala +package com.acme.myapp.model + +class Person ... +``` + +По соглашению все имена пакетов должны быть строчными, +а формальным соглашением об именах является _\.\.\.\_. + +Хотя это и не обязательно, имена пакетов обычно совпадают с именами иерархии каталогов. +Поэтому, если следовать этому соглашению, класс `Person` в этом проекте будет найден +в файле _MyApp/src/main/scala/com/acme/myapp/model/Person.scala_. + + +### Использование нескольких пакетов в одном файле + +Показанный выше синтаксис применяется ко всему исходному файлу: +все определения в файле `Person.scala` принадлежат пакету `com.acme.myapp.model` +в соответствии с предложением `package` в начале файла. + +В качестве альтернативы можно написать `package`, которые применяются только к содержащимся в них определениям: + +```scala +package users: + + package administrators: // полное имя пакета - users.administrators + class AdminUser // полное имя класса - users.administrators.AdminUser + + package normalusers: // полное имя пакета - users.normalusers + class NormalUser // полное имя класса - users.normalusers.NormalUser +``` + +Обратите внимание, что за именами пакетов следует двоеточие, а определения внутри пакета имеют отступ. + +Преимущество этого подхода заключается в том, что он допускает вложение пакетов +и обеспечивает более очевидный контроль над областью видимости и инкапсуляцией, особенно в пределах одного файла. + + +## Операторы импорта + +Операторы импорта используются для доступа к сущностям в других пакетах. +Операторы импорта делятся на две основные категории: + +- импорт классов, трейтов, объектов, функций и методов +- импорт `given` предложений + +Первая категория операторов импорта аналогична тому, что использует Java, +с немного другим синтаксисом, обеспечивающим большую гибкость. +Пример: + +```` +import users.* // импортируется все из пакета `users` +import users.User // импортируется только класс `User` +import users.{User, UserPreferences} // импортируются только два члена пакета +import users.{UserPreferences as UPrefs} // переименование импортированного члена +```` + +Эти примеры предназначены для того, чтобы дать представление о том, как работает первая категория операторов `import`. +Более подробно они объясняются в следующих подразделах. + +Операторы импорта также используются для импорта `given` экземпляров в область видимости. +Они обсуждаются в конце этой главы. + +> import не требуется для доступа к членам одного и того же пакета. + + +### Импорт одного или нескольких членов + +В Scala импортировать один элемент из пакета можно следующим образом: + +```scala +import scala.concurrent.Future +``` + +несколько: + +```scala +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.blocking +``` + +При импорте нескольких элементов их можно импортировать более лаконично: + +```scala +import scala.concurrent.{Future, Promise, blocking} +``` + +Если необходимо импортировать все из пакета _scala.concurrent_, используется такой синтаксис: + +```scala +import scala.concurrent.* +``` + + +### Переименование элементов при импорте + +Иногда необходимо переименовать объекты при их импорте, чтобы избежать конфликтов имен. +Например, если нужно использовать Scala класс `List` вместе с `java.util.List`, +то можно переименовать `java.util.List` при импорте: + +```scala +import java.util.{List as JavaList} +``` + +Теперь имя `JavaList` можно использовать для ссылки на класс `java.util.List` +и использовать `List` для ссылки на Scala класс `List`. + +Также можно переименовывать несколько элементов одновременно, используя следующий синтаксис: + +```scala +import java.util.{Date as JDate, HashMap as JHashMap, *} +``` + +В этой строке кода говорится следующее: “Переименуйте классы `Date` и `HashMap`, как показано, +и импортируйте все остальное из пакета `java.util`, не переименовывая”. + + +### Скрытие членов при импорте + +При импорте часть объектов можно _скрывать_. +Следующий оператор импорта скрывает класс `java.util.Random`, +в то время как все остальное в пакете `java.util` импортируется: + +```scala +import java.util.{Random as _, *} +``` + +Если попытаться получить доступ к классу `Random`, то выдается ошибка, +но есть доступ ко всем остальным членам пакета `java.util`: + +```scala +val r = new Random // не скомпилируется +new ArrayList // доступ есть +``` + +#### Скрытие нескольких элементов + +Чтобы скрыть в import несколько элементов, их можно перечислить перед использованием постановочного знака: + +```scala +scala> import java.util.{List as _, Map as _, Set as _, *} +``` + +Перечисленные классы скрыты, но можно использовать все остальное в _java.util_: + +```scala +scala> new ArrayList[String] +val res0: java.util.ArrayList[String] = [] +``` + +Поскольку эти Java классы скрыты, можно использовать классы Scala `List`, `Set` и `Map` без конфликта имен: + +```scala +scala> val a = List(1, 2, 3) +val a: List[Int] = List(1, 2, 3) + +scala> val b = Set(1, 2, 3) +val b: Set[Int] = Set(1, 2, 3) + +scala> val c = Map(1 -> 1, 2 -> 2) +val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2) +``` + + +### Импорт можно использовать в любом месте + +В Scala операторы `import` могут быть объявлены где угодно. +Их можно использовать в верхней части файла исходного кода: + +```scala +package foo + +import scala.util.Random + +class ClassA: + def printRandom(): Unit = + val r = new Random // класс Random здесь доступен + // ещё код... +``` + +Также операторы `import` можно использовать ближе к тому месту, где они необходимы: + +```scala +package foo + +class ClassA: + import scala.util.Random // внутри ClassA + def printRandom(): Unit = + val r = new Random + // ещё код... + +class ClassB: + // класс Random здесь невидим + val r = new Random // этот код не скомпилится +``` + + +### “Статический” импорт + +Если необходимо импортировать элементы способом, аналогичным подходу “статического импорта” в Java, +то есть для того, чтобы напрямую обращаться к членам класса, не добавляя к ним префикс с именем класса, +используется следующий подход. + +Синтаксис для импорта всех статических членов Java класса `Math`: + +```scala +import java.lang.Math.* +``` + +Теперь можно получить доступ к статическим методам класса `Math`, +таким как `sin` и `cos`, без необходимости предварять их именем класса: + +```scala +import java.lang.Math.* + +val a = sin(0) // 0.0 +val b = cos(PI) // -1.0 +``` + + +### Пакеты, импортированные по умолчанию + +Два пакета неявно импортируются во все файлы исходного кода: + +- `java.lang.*` +- `scala.*` + +Члены object `Predef` также импортируются по умолчанию. + +> Например, такие классы, как `List`, `Vector`, `Map` и т.д. можно использовать явно, не импортируя их - +> они доступны, потому что определены в object `Predef` + + +### Обработка конфликтов имен + +Если необходимо импортировать что-то из корня проекта и возникает конфликт имен, +достаточно просто добавить к имени пакета префикс `_root_`: + +``` +package accounts + +import _root_.accounts.* +``` + + +## Импорт экземпляров `given` + +Как будет показано в главе [“Контекстные абстракции”][contextual], +для импорта экземпляров `given` используется специальная форма оператора `import`. +Базовая форма показана в этом примере: + +```scala +object A: + class TC + given tc as TC + def f(using TC) = ??? + +object B: + import A.* // импорт всех non-given членов + import A.given // импорт экземпляров given +``` + +В этом коде предложение `import A.*` объекта `B` импортирует все элементы `A`, _кроме_ `given` экземпляра `tc`. +И наоборот, второй импорт, `import A.given`, импортирует _только_ `given` экземпляр. +Два предложения импорта также могут быть объединены в одно: + +```scala +object B: + import A.{given, *} +``` + +### Обсуждение + +Селектор с подстановочным знаком `*` помещает в область видимости все определения, кроме `given`, +тогда как селектор выше помещает в область действия все данные, включая те, которые являются результатом расширений. + +Эти правила имеют два основных преимущества: + +- более понятно, откуда берутся данные given. + В частности, невозможно скрыть импортированные given в длинном списке других импортируемых подстановочных знаков. +- есть возможность импортировать все given, не импортируя ничего другого. + Это особенно важно, поскольку given могут быть анонимными, поэтому обычное использование именованного импорта нецелесообразно. + + +### Импорт по типу + +Поскольку given-ы могут быть анонимными, не всегда практично импортировать их по имени, +и вместо этого обычно используется импорт подстановочных знаков. +_Импорт по типу_ предоставляет собой более конкретную альтернативу импорту с подстановочными знаками, +делая понятным то, что импортируется. + +```scala +import A.{given TC} +``` + +Этот код импортирует из `A` любой `given` тип, соответствующий `TC`. +Импорт данных нескольких типов `T1,...,Tn` выражается несколькими `given` селекторами: + +```scala +import A.{given T1, ..., given Tn} +``` + +Импорт всех `given` экземпляров параметризованного типа достигается аргументами с подстановочными знаками. +Например, есть такой `объект`: + +```scala +object Instances: + given intOrd as Ordering[Int] + given listOrd[T: Ordering] as Ordering[List[T]] + given ec as ExecutionContext = ... + given im as Monoid[Int] +``` + +Оператор `import` ниже импортирует экземпляры `intOrd`, `listOrd` и `ec`, но пропускает экземпляр `im`, +поскольку он не соответствует ни одному из указанных шаблонов: + +```scala +import Instances.{given Ordering[?], given ExecutionContext} +``` + +Импорт по типу можно смешивать с импортом по имени. +Если оба присутствуют в предложении import, импорт по типу идет последним. +Например, это предложение импорта импортирует `im`, `intOrd` и `listOrd`, но не включает `ec`: + +```scala +import Instances.{im, given Ordering[?]} +``` + + +### Пример + +В качестве конкретного примера представим, что у нас есть объект `MonthConversions`, +который содержит два определения `given`: + +```scala +object MonthConversions: + trait MonthConverter[A]: + def convert(a: A): String + + given intMonthConverter: MonthConverter[Int] with + def convert(i: Int): String = + i match + case 1 => "January" + case 2 => "February" + // остальные случаи здесь ... + + given stringMonthConverter: MonthConverter[String] with + def convert(s: String): String = + s match + case "jan" => "January" + case "feb" => "February" + // остальные случаи здесь ... +``` + +Чтобы импортировать эти given-ы в текущую область, используем два оператора `import`: + +```scala +import MonthConversions.* +import MonthConversions.{given MonthConverter[?]} +``` + +Теперь создаем метод, использующий эти экземпляры: + +```scala +def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String = + monthConverter.convert(a) +``` + +Вызов метода: + +```scala +@main def main = + println(genericMonthConverter(1)) // January + println(genericMonthConverter("jan")) // January +``` + +Как уже упоминалось ранее, одно из ключевых преимуществ синтаксиса “import given” состоит в том, +чтобы прояснить, откуда берутся данные в области действия, +и в `import` операторах выше ясно, что данные поступают из объекта `MonthConversions`. + + +[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %}