Scala. Профессиональное программирование [Мартин Одерски] (pdf) читать онлайн

-  Scala. Профессиональное программирование  5.72 Мб, 608с. скачать: (pdf) - (pdf+fbd)  читать: (полностью) - (постранично) - Мартин Одерски

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Профессиональное программирование

Scala

5-е издание
Мартин Одерски
Лекс Спун
Билл Веннерс
Фрэнк Соммерс

2022

ББК 32.973.2-018.1
УДК 004.43
О-41

Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс Фрэнк
О-41 Scala. Профессиональное программирование. 5-е изд.. — СПб.: Питер,
2022. — 608 с.: ил. — (Серия «Библиотека программиста»).
ISBN 978-5-4461-1914-1
«Scala. Профессиональное программирование» — главная книга по Scala, популярному
языку для платформы Java, в котором сочетаются концепции объектно-ориентированного
и функционального программирования, благодаря чему он превращается в уникальное
и мощное средство разработки.
Этот авторитетный труд, написанный создателями Scala, поможет вам пошагово изучить
язык и идеи, лежащие в его основе.
Пятое издание значительно обновлено, чтобы охватить многочисленные изменения, появившиеся в Scala 3.

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)
ББК 32.973.2-018.1
УДК 004.43

Права на издание получены по соглашению с Artima Inc. Все права защищены. Никакая часть данной
книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством
как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет
ответственности за возможные ошибки, связанные с использованием книги. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент
подготовки книги к изданию все ссылки на интернет-ресурсы были действующими.

ISBN 978-0997148008 англ.
ISBN 978-5-4461-1914-1

© 2019 Martin Odersky, Lex Spoon, Bill Venners, Frank Sommers.
All rights reserved
© Перевод на русский язык ООО «Прогресс книга», 2022
© Издание на русском языке, оформление ООО «Прогресс книга»,
2022
© Серия «Библиотека программиста», 2022
© Павлов А., перевод с английского языка, 2022

Краткое содержание

Отзывы на предыдущие издания книги . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

18

Предисловие . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

22

Благодарности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27

Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

Глава 1. Масштабируемый язык . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

37

Глава 2. Первые шаги в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

56

Глава 3. Дальнейшие шаги в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69

Глава 4. Классы и объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91

Глава 5. Основные типы и операции . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

104

Глава 6. Функциональные объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

125

Глава 7. Встроенные управляющие конструкции . . . . . . . . . . . . . . . . . . . .

145

Глава 8. Функции и замыкания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

168

Глава 9. Управляющие абстракции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

189

Глава 10. Композиция и наследование . . . . . . . . . . . . . . . . . . . . . . . . . . .

203

Глава 11. Трейты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

229

Глава 12. Пакеты, импорты и экспорты . . . . . . . . . . . . . . . . . . . . . . . . . . .

247

Глава 13. Сопоставление с образцом . . . . . . . . . . . . . . . . . . . . . . . . . . . .

266

Глава 14. Работа со списками . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

299

Глава 15. Работа с другими коллекциями . . . . . . . . . . . . . . . . . . . . . . . . .

331

6   Краткое содержание

Глава 16. Изменяемые объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

350

Глава 17. Иерархия Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

372

Глава 18. Параметризация типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

388

Глава 19. Перечисления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

410

Глава 20. Абстрактные члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

421

Глава 21. Гивены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

447

Глава 22. Методы расширения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

468

Глава 23. Классы типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

482

Глава 24. Углубленное изучение коллекций . . . . . . . . . . . . . . . . . . . . . . .

511

Глава 25. Утверждения и тесты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

576

Глоссарий . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

588

Библиография . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

604

Об авторах . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

607

Оглавление

Отзывы на предыдущие издания книги . . . . . . . . . . . . . . . . . . . . . . . . . 18
Предисловие . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Благодарности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Целевая аудитория . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Как пользоваться книгой . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

Как изучать Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

32

Условные обозначения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

Структура книги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

Ресурсы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

35

Исходный код . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

От издательства . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

Глава 1. Масштабируемый язык . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1. Язык, который растет вместе с вами . . . . . . . . . . . . . . . . . . . . . . . . .

37
38

Растут новые типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Растут новые управляющие конструкции . . . . . . . . . . . . . . . . . . . . 41
1.2. Почему язык Scala масштабируемый? . . . . . . . . . . . . . . . . . . . . . . . .

41

Scala — объектно-ориентированный язык . . . . . . . . . . . . . . . . . . . 42
Scala — функциональный язык . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
1.3. Почему именно Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

45

Scala — совместимый язык . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Scala — лаконичный язык . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Scala — высокоуровневый язык . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Scala — статически типизированный язык . . . . . . . . . . . . . . . . . . . 50
1.4. Истоки Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

8   Оглавление

Глава 2. Первые шаги в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Шаг 1. Осваиваем Scala REPL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

56
57

Шаг 2. Объявляем переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

Шаг 3. Определяем функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

60

Шаг 4. Пишем Scala-скрипты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

62

Шаг 5. Организуем цикл с while и принимаем решение с if . . . . . . . . . . . .

64

Шаг 6. Перебираем элементы с foreach и for-do . . . . . . . . . . . . . . . . . . . .

66

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

68

Глава 3. Дальнейшие шаги в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Шаг 7. Параметризуем массивы типами . . . . . . . . . . . . . . . . . . . . . . . . . .

69
69

Шаг 8. Используем списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

73

Шаг 9. Используем кортежи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

78

Шаг 10. Используем множества и отображения . . . . . . . . . . . . . . . . . . . .

79

Шаг 11. Учимся распознавать функциональный стиль . . . . . . . . . . . . . . .

84

Шаг 12. Преобразование с отображениями и for-yield . . . . . . . . . . . . . . .

87

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

90

Глава 4. Классы и объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1. Классы, поля и методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91
91

4.2. Когда подразумевается использование точки с запятой . . . . . . . . . .

96

4.3. Объекты-одиночки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

96

4.4. Case-классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

99

4.5. Приложение на языке Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

101

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

103

Глава 5. Основные типы и операции . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1. Некоторые основные типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

104
104

5.2. Литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

105

Целочисленные литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Литералы чисел с плавающей точкой . . . . . . . . . . . . . . . . . . . . . 107
Большие числовые литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Символьные литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Строковые литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Булевы литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.3. Интерполяция строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

110

5.4. Все операторы являются методами . . . . . . . . . . . . . . . . . . . . . . . . .

112

5.5. Арифметические операции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

115

5.6. Отношения и логические операции . . . . . . . . . . . . . . . . . . . . . . . . .

116

Оглавление   9

5.7. Поразрядные операции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

117

5.8. Равенство объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

119

5.9. Приоритет и ассоциативность операторов . . . . . . . . . . . . . . . . . . . . 120
5.10. Обогащающие операции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

123

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

124

Глава 6. Функциональные объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1. Спецификация класса Rational . . . . . . . . . . . . . . . . . . . . . . . . . . . .

125
125

6.2. Конструирование класса Rational . . . . . . . . . . . . . . . . . . . . . . . . . .

126

6.3. Переопределение метода toString . . . . . . . . . . . . . . . . . . . . . . . . . .

128

6.4. Проверка соблюдения предварительных условий . . . . . . . . . . . . . .

129

6.5. Добавление полей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

130

6.6. Собственные ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

132

6.7. Вспомогательные конструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . .

132

6.8. Приватные поля и методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

134

6.9. Определение операторов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

135

6.10. Идентификаторы в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

137

6.11. Перегрузка методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

140

6.12. Методы расширения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

142

6.13. Предостережение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

143

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

143

Глава 7. Встроенные управляющие конструкции . . . . . . . . . . . . . . . . . . . .
7.1. Выражения if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

145
146

7.2. Циклы while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

147

7.3. Выражения for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

150

Обход элементов коллекций . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Фильтрация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Вложенные итерации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Привязки промежуточных переменных . . . . . . . . . . . . . . . . . . . . 153
Создание новой коллекции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.4. Обработка исключений с помощью выражений try . . . . . . . . . . . . .

155

Генерация исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Перехват исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Условие finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Выдача значения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.5. Выражения match . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

158

7.6. Программирование без break и continue . . . . . . . . . . . . . . . . . . . . .

160

10   Оглавление

7.7. Область видимости переменных . . . . . . . . . . . . . . . . . . . . . . . . . . .

162

7.8. Рефакторинг кода, написанного в императивном стиле . . . . . . . . . .

165

7.9. Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

167

Глава 8. Функции и замыкания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.1. Методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

168
168

8.2. Локальные функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

169

8.3. Функции первого класса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

171

8.4. Краткие формы функциональных литералов . . . . . . . . . . . . . . . . . .

173

8.5. Синтаксис заместителя . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

173

8.6. Частично примененные функции . . . . . . . . . . . . . . . . . . . . . . . . . .

174

8.7. Замыкания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

177

8.8. Специальные формы вызова функций . . . . . . . . . . . . . . . . . . . . . . .

180

Повторяющиеся параметры . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Именованные аргументы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Значения параметров по умолчанию . . . . . . . . . . . . . . . . . . . . . . 182
8.9. Тип SAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

183

8.10. Хвостовая рекурсия . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

184

Трассировка функций с хвостовой рекурсией . . . . . . . . . . . . . . . 185
Ограничения хвостовой рекурсии . . . . . . . . . . . . . . . . . . . . . . . . 187
Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

188

Глава 9. Управляющие абстракции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.1. Сокращение повторяемости кода . . . . . . . . . . . . . . . . . . . . . . . . . .

189
189

9.2. Упрощение клиентского кода . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

193

9.3. Карринг . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

195

9.4. Создание новых управляющих конструкций . . . . . . . . . . . . . . . . . .

196

9.5. Передача параметров по имени . . . . . . . . . . . . . . . . . . . . . . . . . . .

199

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

202

Глава 10. Композиция и наследование . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.1. Библиотека двумерной разметки . . . . . . . . . . . . . . . . . . . . . . . . . .

203
203

10.2. Абстрактные классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

204

10.3. Определяем методы без параметров . . . . . . . . . . . . . . . . . . . . . . .

205

10.4. Расширяем классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

208

10.5. Переопределяем методы и поля . . . . . . . . . . . . . . . . . . . . . . . . . .

210

10.6. Определяем параметрические поля . . . . . . . . . . . . . . . . . . . . . . .

211

10.7. Вызываем конструктор суперкласса . . . . . . . . . . . . . . . . . . . . . . . .

212

Оглавление   11

10.8. Используем модификатор override . . . . . . . . . . . . . . . . . . . . . . . . .

213

10.9. Полиморфизм и динамическое связывание . . . . . . . . . . . . . . . . . .

215

10.10. Объявляем финальные элементы . . . . . . . . . . . . . . . . . . . . . . . .

217

10.11. Используем композицию и наследование . . . . . . . . . . . . . . . . . .

218

10.12. Реализуем методы above, beside и toString . . . . . . . . . . . . . . . . . . 220
10.13. Определяем фабричный объект . . . . . . . . . . . . . . . . . . . . . . . . .

223

10.14. Методы heighten и widen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

224

10.15. Собираем все вместе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

228

Глава 11. Трейты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11.1. Как работают трейты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

229
229

11.2. Сравнение «тонких» и «толстых» интерфейсов . . . . . . . . . . . . . . .

232

11.3. Трейты как наращиваемые модификации . . . . . . . . . . . . . . . . . . .

235

11.4. Почему не используется множественное наследование . . . . . . . . .

239

11.5. Параметры трейтов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

243

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

245

Глава 12. Пакеты, импорты и экспорты . . . . . . . . . . . . . . . . . . . . . . . . . . .
12.1. Помещение кода в пакеты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

247
247

12.2. Краткая форма доступа к родственному коду . . . . . . . . . . . . . . . .

249

12.3. Импортирование кода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

252

12.4. Неявное импортирование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

255

12.5. Модификаторы доступа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

256

Приватные члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Защищенные члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Публичные члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Область защиты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Видимость и объекты-компаньоны . . . . . . . . . . . . . . . . . . . . . . . 260
12.6. Определения верхнего уровня . . . . . . . . . . . . . . . . . . . . . . . . . . .

261

12.7. Экспорты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

262

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

265

Глава 13. Сопоставление с образцом . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1. Простой пример . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

266
266

case-классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Сопоставление с образцом . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Сравнение match со switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270

12   Оглавление

13.2. Разновидности паттернов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

271

Подстановочные паттерны . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Паттерны-константы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Патерны-переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Паттерны-конструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Паттерны-последовательности . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Паттерны-кортежи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Типизированные паттерны . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Затирание типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
Привязка переменной . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
13.3. Ограждение образца . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

280

13.4. Наложение паттернов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

281

13.5. Запечатанные классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

283

13.6. Сопоставление паттерна Options . . . . . . . . . . . . . . . . . . . . . . . . . .

285

13.7. Паттерны повсюду . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

286

Паттерны в определениях переменных . . . . . . . . . . . . . . . . . . . . 286
Последовательности вариантов в качестве частично
примененных функций . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
Паттерны в выражениях for . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
13.8. Большой пример . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

291

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

298

Глава 14. Работа со списками . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
14.1. Литералы списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
14.2. Тип List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

300

14.3. Создание списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

300

14.4. Основные операции над списками . . . . . . . . . . . . . . . . . . . . . . . . .

301

14.5. Паттерны-списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

302

14.6. Методы первого порядка класса List . . . . . . . . . . . . . . . . . . . . . . .

304

Конкатенация двух списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
Принцип «разделяй и властвуй» . . . . . . . . . . . . . . . . . . . . . . . . . 305
Получение длины списка: length . . . . . . . . . . . . . . . . . . . . . . . . . 307
Обращение к концу списка: init и last . . . . . . . . . . . . . . . . . . . . . 307
Реверсирование списков: reverse . . . . . . . . . . . . . . . . . . . . . . . . 308
Префиксы и суффиксы: drop, take и splitAt . . . . . . . . . . . . . . . . . 309
Выбор элемента: apply и indices . . . . . . . . . . . . . . . . . . . . . . . . . 310
Линеаризация списка списков: flatten . . . . . . . . . . . . . . . . . . . . . 310
Объединение списков: zip и unzip . . . . . . . . . . . . . . . . . . . . . . . . 311
Отображение списков: toString и mkString . . . . . . . . . . . . . . . . . . 311

Оглавление   13

Преобразование списков: iterator, toArray, copyToArray . . . . . . . . . 312
Пример: сортировка слиянием . . . . . . . . . . . . . . . . . . . . . . . . . . 313
14.7. Методы высшего порядка класса List . . . . . . . . . . . . . . . . . . . . . . .

315

Отображения списков: map, flatMap и foreach . . . . . . . . . . . . . . . 316
Фильтрация списков: filter, partition, find, takeWhile,
dropWhile и span . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Применение предикатов к спискам: forall и exists . . . . . . . . . . . . 318
Свертка списков: foldLeft и foldRight . . . . . . . . . . . . . . . . . . . . . . 319
Пример: реверсирование списков с помощью свертки . . . . . . . . . 321
Сортировка списков: sortWith . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
14.8. Методы объекта List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

323

Создание списков из их элементов: List.apply . . . . . . . . . . . . . . . 323
Создание диапазона чисел: List.range . . . . . . . . . . . . . . . . . . . . . 323
Создание единообразных списков: List.fill . . . . . . . . . . . . . . . . . . 324
Табулирование функции: List.tabulate . . . . . . . . . . . . . . . . . . . . . 324
Конкатенация нескольких списков: List.concat . . . . . . . . . . . . . . . 324
14.9. Совместная обработка нескольких списков . . . . . . . . . . . . . . . . . .

325

14.10. Понимание имеющегося в Scala алгоритма вывода типов . . . . . .

326

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

330

Глава 15. Работа с другими коллекциями . . . . . . . . . . . . . . . . . . . . . . . . .
15.1. Последовательности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

331
331

Списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
Массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Буферы списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
Буферы массивов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
Строки (реализуемые через StringOps) . . . . . . . . . . . . . . . . . . . . 334
15.2. Множества и отображения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

335

Использование множеств . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
Применение отображений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Множества и отображения, используемые по умолчанию . . . . . . 340
Отсортированные множества и отображения . . . . . . . . . . . . . . . 341
15.3. Выбор между изменяемыми или неизменяемыми коллекциями . . .

342

15.4. Инициализация коллекций . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

344

Преобразование в массив или список . . . . . . . . . . . . . . . . . . . . . 346
Преобразования между изменяемыми и неизменяемыми
множествами и отображениями . . . . . . . . . . . . . . . . . . . . . . . . . 347
15.5. Кортежи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

347

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

349

14   Оглавление

Глава 16. Изменяемые объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.1. Что делает объект изменяемым . . . . . . . . . . . . . . . . . . . . . . . . . .

350
350

16.2. Переназначаемые переменные и свойства . . . . . . . . . . . . . . . . . .

352

16.3. Практический пример: моделирование дискретных событий . . . . .

356

16.4. Язык для цифровых схем . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

357

16.5. API моделирования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

360

16.6. Моделирование электронной логической схемы . . . . . . . . . . . . . .

364

Класс Wire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Метод inverter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
Методы andGate и orGate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
Вывод симуляции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
Запуск симулятора . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

370

Глава 17. Иерархия Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17.1. Иерархия классов Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

372
372

17.2. Как реализованы примитивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

376

17.3. Низшие типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

378

17.4. Определение собственных классов значений . . . . . . . . . . . . . . . . .

379

Уход от монокультурности типов . . . . . . . . . . . . . . . . . . . . . . . . . 380
17.5. Типы пересечений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

382

17.6. Типы объединения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

383

17.7. Прозрачные трейты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

386

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

387

Глава 18. Параметризация типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18.1. Функциональные очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

388
388

18.2. Сокрытие информации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

392

Приватные конструкторы и фабричные методы . . . . . . . . . . . . . . 392
Альтернативный вариант: приватные классы . . . . . . . . . . . . . . . 393
18.3. Аннотации вариантности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

394

Вариантность и массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
18.4. Проверка аннотаций вариантности . . . . . . . . . . . . . . . . . . . . . . . .

399

18.5. Нижние ограничители . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

402

18.6. Контравариантность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

404

18.7. Верхние ограничители . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

407

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

409

Оглавление   15

Глава 19. Перечисления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19.1. Перечисляемые типы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . .

410
410

19.2. Алгебраические типы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . .

414

19.3. Обобщенные ADT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

416

19.4. Что делает типы ADT алгебраическими . . . . . . . . . . . . . . . . . . . . .

417

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

420

Глава 20. Абстрактные члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20.1. Краткий обзор абстрактных членов . . . . . . . . . . . . . . . . . . . . . . . .

421
421

20.2. Члены-типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

422

20.3. Абстрактные val-переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . .

423

20.4. Абстрактные var-переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . .

424

20.5. Инициализация абстрактных val-переменных . . . . . . . . . . . . . . . .

425

Параметрические поля трейтов . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Ленивые val-переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
20.6. Абстрактные типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

431

20.7. Типы, зависящие от пути . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

434

20.8. Уточняющие типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

436

20.9. Практический пример: работа с валютой . . . . . . . . . . . . . . . . . . .

437

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

446

Глава 21. Гивены . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21.1. Как это работает . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

447
448

21.2. Параметризованные given-типы . . . . . . . . . . . . . . . . . . . . . . . . . .

451

21.3. Анонимные given-экземпляры . . . . . . . . . . . . . . . . . . . . . . . . . . . .

455

21.4. Параметризованные given-экземпляры в виде
классов типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

456

21.5. Импорт гивенов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

459

21.6. Правила для контекстных параметров . . . . . . . . . . . . . . . . . . . . . .

461

21.7. Когда подходит сразу несколько гивенов . . . . . . . . . . . . . . . . . . . .

463

21.8. Отладка гивенов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

465

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

467

Глава 22. Методы расширения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22.1. Основы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

468
468

22.2. Обобщенные расширения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

471

22.3. Групповые расширения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

472

22.4. Использование класса типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

474

16   Оглавление

22.5. Методы расширения для заданных экземпляров . . . . . . . . . . . . . .

477

22.6. Где Scala ищет методы расширения . . . . . . . . . . . . . . . . . . . . . . .

480

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

481

Глава 23. Классы типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23.1. Зачем нужны классы типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

482
482

23.2. Границы контекста . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

487

23.3. Главные методы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

490

23.4. Многостороннее равенство . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

494

23.5. Неявные преобразования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

499

23.6. Пример использования класса типов: сериализация JSON . . . . . . .

502

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

510

Глава 24. Углубленное изучение коллекций . . . . . . . . . . . . . . . . . . . . . . .
24.1. Изменяемые и неизменяемые коллекции . . . . . . . . . . . . . . . . . . . .

511
513

24.2. Согласованность коллекций . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

514

24.3. Трейт Iterable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

516

Подкатегории Iterable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523
24.4. Трейты последовательностей Seq, IndexedSeq и LinearSeq . . . . . . . 523
Буферы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528
24.5. Множества . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

530

24.6. Отображения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

534

24.7. Конкретные классы неизменяемых коллекций . . . . . . . . . . . . . . . .

538

Списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
Ленивые списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
Неизменяемые ArraySeq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540
Векторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541
Неизменяемые очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542
Диапазоны . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543
Сжатые коллекции HAMT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543
Красно-черные деревья . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Неизменяемые битовые множества . . . . . . . . . . . . . . . . . . . . . . . 544
Векторные отображения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Списочные отображения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
24.8. Конкретные классы изменяемых коллекций . . . . . . . . . . . . . . . . . .

546

Буферы массивов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Буферы списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Построители строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547

Оглавление   17

ArrayDeque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
Очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
Стеки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548
Изменяемые ArraySeq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548
Хеш-таблицы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548
Слабые хеш-отображения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549
Совместно используемые отображения . . . . . . . . . . . . . . . . . . . . 550
Изменяемые битовые множества . . . . . . . . . . . . . . . . . . . . . . . . . 550
24.9. Массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

551

24.10. Строки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

555

24.11. Характеристики производительности . . . . . . . . . . . . . . . . . . . . . . 556
24.12. Равенство . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

558

24.13. Представления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

559

24.14. Итераторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

563

Буферизованные итераторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
24.15. Создание коллекций с нуля . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

571

24.16. Преобразования между коллекциями Java и Scala . . . . . . . . . . . .

573

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

575

Глава 25. Утверждения и тесты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25.1. Утверждения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

576
576

25.2. Тестирование в Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

578

25.3. Информативные отчеты об ошибках . . . . . . . . . . . . . . . . . . . . . . .

579

25.4. Использование тестов в качестве спецификаций . . . . . . . . . . . . . .

581

25.5. Тестирование на основе свойств . . . . . . . . . . . . . . . . . . . . . . . . . .

584

25.6. Подготовка и проведение тестов . . . . . . . . . . . . . . . . . . . . . . . . . .

586

Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

587

Глоссарий . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588
Библиография . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 604
Об авторах . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607

Отзывы на предыдущие
издания книги

Это, вероятно, одна из лучших книг по программированию, которые я ко­
гда-либо читал. Мне нравятся стиль изложения, краткость и подробные
объяснения. Книга, кажется, отвечает на каждый вопрос, который приходит
мне в голову, — она всегда на шаг впереди меня. Авторы не просто дают вам
код — они объясняют самую суть, чтобы вы действительно понимали, о чем
идет речь. Мне это очень нравится.
Кен Эджервари (Ken Egervari),
ведущий разработчик программного обеспечения
Эта книга написана четко, основательно и понятно. В ней есть отличные
примеры и полезные советы. Она позволила нашей организации быстро
и эффективно освоить язык Scala. Эта книга отлично подходит для любого
программиста, который хочет разобраться в гибкости и элегантности этого
языка.
Ларри Моррони (Larry Morroni),
владелец Morroni Technologies, Inc.
Это отличный учебник по языку Scala. В нем хорошо проработана каждая
глава, основанная на концепциях и примерах, описанных в предыдущих
главах. Подробно объясняются конструкции языка, часто приводятся при­
меры того, чем язык отличается от Java. Помимо основного языка, также
рассматриваются такие библиотеки, как контейнеры и акторы.
С этим пособием действительно легко работать, и, вероятно, это одна из луч­
ших технических книг, которые я читал за последнее время. Я настоятельно
рекомендую эту книгу любому программисту, желающему узнать больше
о языке Scala.
Мэтью Тодд (Matthew Todd)

Отзывы на предыдущие издания книги   19

Я поистине впечатлен работой, проделанной авторами этой книги. Она бес­
ценное руководство по языку Scala — средству для эффективного написания
кода, постоянный источник вдохновения для разработки и реализации мас­
штабируемого ПО. Если бы только у меня была Scala в ее нынешнем зрелом
состоянии и эта книга на моем столе еще в 2003 году, когда я участвовал
в разработке и внедрении части инфраструктуры портала Олимпийских игр
2004 года в Афинах!
Всем читателям: независимо от вашего опыта, я думаю, программирование на
Scala покажется вам очень гибким, и эта книга станет вашим верным другом
в этом путешествии.
Христос К. К. Ловердос (Christos KK Loverdos),
консультант по программному обеспечению, исследователь
«Scala. Профессиональное программирование» — это превосходное углуб­
ленное введение в Scala, а также отличный справочник. Я бы сказал, что эта
книга занимает видное место на моей полке, если не считать, что я почти
везде ношу ее с собой.
Брайан Клэппер (Brian Clapper),
президент ArdenTex, Inc.
Отличная книга, хорошо написанная, с вдумчивыми примерами. Рекомен­
дую как опытным программистам, так и новичкам.
Говард Ловатт (Howard Lovatt)
Эта книга рассказывает не только о том, как разрабатывать программы на
языке Scala, но и, что более важно, о том, зачем это делать. Прагматичный
подход книги к представлению возможностей сочетания объектно-ориен­
тированного и функционального программирования не оставляет читателю
никаких сомнений в том, что такое Scala на самом деле.
Доктор Эрвин Варга (Ervin Varga),
генеральный директор и основатель EXPRO I.T. Consulting
Это отличное введение в функциональное программирование для объектноориентированных программистов. Моей основной целью было изучение
функционального программирования, и на этом пути меня поджидали не­
которые приятные сюрпризы Scala, такие как case-классы и сопоставление
с образцом. Scala — интригующий язык, и эта книга хорошо его раскрывает.

20   Отзывы на предыдущие издания книги

В подобного рода книгах всегда есть риск сказать слишком много или слиш­
ком мало. Я считаю, что в «Scala. Профессиональное программирование» как
раз достигнут идеальный баланс.
Джефф Хон (Jeff Heon), программист-аналитик
Понятность и техническая полнота — отличительные черты любой хорошо
написанной книги, и я поздравляю Мартина Одерски, Лекса Спуна и Билла
Веннерса с действительно очень хорошо выполненной работой! Книга «Scala.
Профессиональное программирование» начинается с описания базовых
концепций и поднимает пользователя до среднего уровня и выше. Эту книгу,
безусловно, необходимо купить всем, кто хочет изучить Scala.
Джаган Намби (Jagan Nambi),
архитектор корпоративных бизнес-решений,
GMAC Financial Services
Читать эту книгу — одно удовольствие. Это одна из тех хорошо написан­
ных технических книг, которые обеспечивают глубокое и всестороннее
освещение предмета исключительно кратким и элегантным образом. Книга
построена очень естественно и логично. Она одинаково хорошо подходит
как для инженера-любителя, который просто хочет быть в курсе текущих
тенденций, так и для профессионала, стремящегося к глубокомупонима­
нию основных функций языка и его дизайна. Настоятельно рекомендую
эту книгу всем, кто интересуется функциональным программированием
в целом. Разработчикам Scala эту книгу, безусловно, необходимо про­
читать.
Игорь Хлыстов, архитектор программного
обеспечения/ведущий программист, Greystone Inc.
По мере изучения книги «Scala. Профессиональное программирование»
начинаешь понимать, какое огромное количество труда в нее вложено.
Я никогда раньше не читал настолько всеобъемлющего пособия для но­
вичков. Большинство авторов, стараясь объяснять материал на доступном
уровне и не запутать читателя, опускают некоторые сложные аспекты.
Это оставляет довольно неприятный осадок, так как возникает навязчи­
вое ощущение, что материал до конца не усвоен. Всегда есть остаточная
«магия», которую не объяснили и о которой читатель вообще не может
судить. В этой книге такого никогда не происходит, она никогда не при­
нимает ничего как должное: подробно описана каждая деталь и, если этого

Отзывы на предыдущие издания книги   21

недостаточно, дается ссылка на более глубокое объяснение. В самом деле,
в тексте много перекрестных ссылок, поэтому составить полную картину
сложной темы относительно легко.
Джеральд Леффлер (Gerald Loeffler),
Java-архитектор корпоративных бизнес-решений
Во времена, когда хорошие книги по программированию редки, «Scala.
Профессиональное программирование» Мартина Одерски, Лекса Спуна
и Билла Веннерса действительно выделяется — это отличное введение для
программистов среднего уровня. Здесь вы найдете все необходимое, чтобы
выучить этот многообещающий язык.
Кристиан Нойкирхен (Christian Neukirchen)

Предисловие

Забавно наблюдать за рождением нового языка программирования. Боль­
шинство тех, кто пользуется языками программирования — будь то новичок
или профессионал, — не задумываются об их происхождении. Подобно
молотку или топору, язык программирования — это инструмент, который
позволяет нам выполнять свою работу. Мы редко размышляем о том, как
появился этот инструмент, каков был процесс его разработки. У нас может
быть свое мнение о его синтаксисе, но обычно просто принимаем его как есть
и движемся вперед.
Однако создание языка программирования открывает совершенно иную
перспективу. Появляются новые возможности того, что могло показаться
безграничным. Но в то же время язык программирования должен удовле­
творять бесконечному списку ограничений. Странное противоречие.
Новые языки программирования создаются по многим причинам: из-за
личного желания сделать что-то свое, глубокой академической проницатель­
ности, технического долга или анализа других архитектур компиляторов
и даже политики. Scala 3 — это комбинация некоторых из них.
Какая бы ни была комбинация, все началось с того, что однажды Мартин
Одерски (Martin Odersky) исчез, появившись несколько дней спустя, чтобы
объявить на собрании исследовательской группы, что он начал эксперимен­
ты по воплощению DOT-исчисления в жизнь, написав новый компилятор
с нуля1.
Мы были группой аспирантов и кандидатов, которые до недавнего времени
играли важную роль в разработке и сопровождении Scala 2. В то время Scala
достигал, казалось, непостижимых высот успеха, особенно для эзотерическо­
го и академического языка программирования из школы с забавным названи­
ем в Швейцарии. Scala недавно стал популярным среди стартапов в Области
1

DOT-исчисления, или зависимые типы объектов, — это попытка теоретически
обосновать систему типов языка Scala.

Предисловие   23

залива Сан-Франциско, и для поддержки, сопровождения и управления вы­
пусками Scala 2 недавно была создана компания Typesafe, позже названная
Lightbend. Так почему же вдруг появился новый компилятор и, возможно,
новый и другой язык программирования? Большинство были настроены
скептически. Мартина это не испугало.
Прошли месяцы. Как по будильнику, в 12 часов дня вся лаборатория стя­
гивалась в коридор, соединяющий все наши офисы. После того как нас со­
биралось изрядное количество, мы вместе с Мартином отправлялись в один
из многочисленных буфетов ФПШЛ (Федеральная политехническая школа
Лозанны), чтобы пообедать, а затем выпить кофе. Каждый день во время
этого ритуала идеи для нового компилятора были постоянной темой для об­
суждения. Обсуждения были жаркими, мы прыгали с одной темы на другую:
от чего-то, что «на 150 %» совместимо со Scala 2 (чтобы избежать фиаско, как
при переходе с Python 2 на Python 3), до создания нового языка с полным
спектром зависимых типов.
Один за другим, все скептики в исследовательской группе в итоге на­
чинали увлекаться каким-либо из аспектов Scala 3, будь то оптимизация
реализации проверки типов, новая архитектура компилятора или мощные
дополнения к системе типов. Со временем большая часть сообщества
также пришла к мысли, что Scala 3 значительно улучшен по сравнению со
Scala 2. У разных людей были разные причины для этого. Для некоторых
это было улучшение читаемости за счет решения сделать необязательными
фигурные и круглые скобки вокруг условий в условных операторах. Для
других это были улучшения в системе типов, например сопоставление
типов для улучшенного программирования на уровне типов. Список был
бесконечным.
Вместо того чтобы слепо продвигаться вперед в разработке Scala 3, осно­
вываясь лишь на догадках, я могу с уверенностью сказать, что Scala 3 — это
результат большого изучения решений прошлого и многолетнего взаимо­
действия с исследовательской группой ФПШЛ и сообществом Scala. И не
было другого выхода, кроме как начать с чистого листа и строить на чистом
фундаменте. Благодаря такому подходу с нуля возник, по сути, новый язык
программирования, и имя ему — Scala 3. Конечно, он может быть совместим
со Scala 2 и считаться третьим крупным релизом уже существующего языка
программирования, но не дайте себя обмануть: Scala 3 представляет собой
существенную оптимизацию многих экспериментальных идей, впервые по­
явившихся в Scala 2.
Возможно, самое уникальное в Scala 3 — это то, что случилось с имплицита­
ми. Scala с момента своего создания использовался умными программистами

24   Предисловие

для достижения функциональности, которую мало кто считал возможной
даже с учетом набора функций Scala, не говоря уже о том, для чего этот язык
был разработан. Функция, ранее известная как имплициты, — это, пожалуй,
самая известная функция Scala, которая применялась для использования
Scala 2 самым неожиданным образом.
Примеры использования имплицитов включают в себя добавление метода
к классу задним числом без расширения и повторной компиляции этого
класса. Или, учитывая сигнатуру типа, используемую в некотором контек­
сте, автоматический выбор правильной реализации для этого контекста. Это
лишь верхушка айсберга — мы даже написали исследовательскую работу,
в которой пытались каталогизировать множество способов, которыми раз­
работчики использовали имплициты [Kri19].
Это все равно что дать пользователю кнопки и рычаги и предоставить ему
возможность построить отлаженный механизм, как механический кальку­
лятор (рис. 1). Но часто вместо этого получается что-то наподобие кинети­
ческой скульптуры Тео Янсена (Theo Jansen) (рис. 2), а не что-то имеющее
очевидное применение1. Проще говоря, вы даете сообществу программистов
нечто столь же простое, как кнопка и рычаг, и бесстрашные люди будут
искать творческие способы их использования. Такова природа человека.
Но, возможно, ошибкой Scala 2 была идея предоставить в первую очередь
что-то столь же универсальное, как кнопки и рычаги.

Рис. 1. Что мы задумали…

1

Рис. 2. …И что получили

Динамичные изображения кинетических скульптур Тео Янсена, получивших назва­
ние Strandbeest, см. в видео: https://www.youtube.com/watch?v=LewVEF2B_pM.

Предисловие   25

Дело здесь в том, что в Scala 2 был бесконечный набор возможностей для
использования имплицитов, что потребовало исследовательской работы,
и сообщество в целом не могло прийти к согласию относительно того, как
разумно их использовать. Никакая языковая функция не должна иметь столь
туманного предназначения. И тем не менее они были. Имплициты рассма­
тривались одними как уникальная и мощная особенность Scala, которой, по
сути, не было ни в одном другом языке, а другими — как загадочный и часто
разочаровывающий механизм, который агрессивно переписывал ваш код,
чтобы он стал чем-то другим.
Возможно, вы слышали часто повторяемую мантру о том, что Scala 3 во
многих отношениях представляет собой упрощение Scala/Scala 2. История
имплицитов — отличный тому пример. Осознавая все кульбиты, которые
программисты делали с имплицитами в попытке реализовать более широкие
шаблоны программирования, такие как вывод классов, Мартин, не без помо­
щи, пришел к выводу, что нам не следует сосредотачиваться на имплицитах
как на механизме, который люди могут использовать в самых общих случаях.
Скорее, мы должны сосредоточиться на том, что программисты хотят де­
лать с имплицитами, и сделать это проще и эффективнее. Отсюда и мантра:
«Scala 3 фокусируется на намерении, а не на механизме».
В Scala 3 вместо того, чтобы сосредоточиться на универсальности имплици­
тов как механизма, было принято решение сосредоточиться на конкретных
сценариях их использования, которые разработчики имели в виду при выбо­
ре имплицитов в первую очередь, и сделать эти шаблоны более доступными
для использования по прямому назначению. Примеры включают в себя пере­
дачу контекстной или конфигурационной информации неявными методами,
без необходимости передавать повторяющиеся аргументы программисту,
добавление методов в классы задним числом и преобразование между типа­
ми, такими как Ints и Doubles, во время вычислений. Теперь Scala 3 делает
эти варианты использования доступными для программистов без необхо­
димости применять некоторую «глубокую» интуицию в отношении того,
как компилятор Scala решит использовать имплициты. Вместо этого вы
можете просто сосредоточиться на таких задачах, как «добавить метод foo
в класс Bar без необходимости его перекомпиляции». Докторская степень
не требуется. Просто замените предыдущее понятие «неявный» другими,
более прямыми ключевыми словами, которые соответствуют конкретным
вариантам использования, например такими, как given и using. Подробнее
об этом читайте в главах 21 и 22.

26   Предисловие

Эта история о том, что «приоритет отдается намерению над механизмом»,
не останавливается на пересмотре имплицитов. Скорее, философия затра­
гивает почти все аспекты языка. Примерами могут служить дополнения
и оптимизация многих аспектов системы типов Scala от типов объедине­
ния и перечислений до сопоставления типов или даже чистки синтакси­
са Scala: необязательные фигурные скобки для улучшения читаемости
и более ­читаемый «тихий» синтаксис для конструкций if, else и while,
в результате чего условные выражения больше напоминают английский
язык, чем машинный код.
Не верьте мне на слово. Независимо от того, являетесь ли вы новичком
в Scala или опытным разработчиком, я надеюсь, что вы найдете новшества,
вошедшие в Scala 3, такими же свежими и простыми, какими их нахожу я!
Хизер Миллер (Heather Miller),
Лозанна, Швейцария,
1 июня 2021 г.

Благодарности

Мы благодарны за вклад в эту книгу многим людям.
Сам язык Scala — плод усилий множества специалистов. Свой вклад в проек­
тирование и реализацию версии 1.0 внесли Филипп Альтер (Philippe Altherr),
Винсент Кремет (Vincent Cremet), Жиль Дюбоше (Gilles Dubochet), Бурак
Эмир (Burak Emir), Стефан Мишель (Stéphane Micheloud), Николай Михай­
лов (Nikolay Mihaylov), Мишель Шинц (Michel Schinz), Эрик Стенман (Erik
Stenman) и Матиас Зенгер (Matthias Zenger). К разработке второй и текущей
версий языка, а также инструментальных средств подключились Фил Багвелл
(Phil Bagwell), Антонио Куней (Antonio Cunei), Юлиан Драгос (Iulian Dragos),
Жиль Дюбоше (Gilles Dubochet), Мигель Гарсиа (Miguel Garcia), Филипп
Халлер (Philipp Haller), Шон Макдирмид (Sean McDirmid), Инго Майер (Ingo
Maier), Донна Малайери (Donna Malayeri), Адриан Мурс (Adriaan Moors),
Хуберт Плоциничак (Hubert Plociniczak), Пол Филлипс (Paul Phillips), Алек­
сандр Прокопец (Aleksandar Prokopec), Тиарк Ромпф (Tiark Rompf), Лукас
Рыц (Lukas Rytz) и Джеффри Уошберн (Geoffrey Washburn).
Следует также упомянуть тех, кто участвовал в работе над структурой языка.
Эти люди любезно делились с нами своими идеями в оживленных и вдох­
новляющих дискуссиях, вносили важные фрагменты кода в открытую раз­
работку и делали весьма ценные замечания по поводу предыдущих версий.
Это Гилад Браха (Gilad Bracha), Натан Бронсон (Nathan Bronson), Коаюан
(Caoyuan), Эймон Кэннон (Aemon Cannon), Крейг Чамберс (Craig Chambers),
Крис Конрад (Chris Conrad), Эрик Эрнст (Erik Ernst), Матиас Феллизен
(Matthias Felleisen), Марк Харра (Mark Harrah), Шрирам Кришнамурти
(Shriram Krishnamurti), Гэри Ливенс (Gary Leavens), Дэвид Макивер (David
MacIver), Себастьян Манит (Sebastian Maneth), Рикард Нильссон (Rickard
Nilsson), Эрик Мейер (Erik Meijer), Лалит Пант (Lalit Pant), Дэвид Поллак
(David Pollak), Джон Претти (Jon Pretty), Клаус Остерман (Klaus Ostermann),
Хорхе Ортис (Jorge Ortiz), Дидье Реми (Didier Rémy), Майлз Сабин (Miles
Sabin), Виджей Сарасват (Vijay Saraswat), Даниэль Спивак (Daniel Spiewak),
Джеймс Страчан (James Strachan), Дон Симе (Don Syme), Эрик Торреборре
(Erik Torreborre), Мэдс Торгерсен (Mads Torgersen), Филип Уодлер (Philip

28   Благодарности

Wadler), Джейми Уэбб (Jamie Webb), Джон Уильямс (John Williams), Кевин
Райт (Kevin Wright) и Джейсон Зауг (Jason Zaugg). Очень полезные отзывы,
которые помогли нам улучшить язык и его инструментальные средства, были
получены от людей, подписанных на наши рассылки по Scala.
Джордж Бергер (George Berger) усердно работал над тем, чтобы процесс
создания и размещения книги в интернете протекал гладко. Как результат,
в данном проекте не было никаких технических сбоев.
Ценные отзывы о начальных вариантах текста книги были получены нами
от многих людей. Наших благодарностей заслуживают Эрик Армстронг
(Eric Armstrong), Джордж Бергер (George Berger), Алекс Блевитт (Alex
Blewitt), Гилад Браха (Gilad Bracha), Уильям Кук (William Cook), Брюс
Экель (Bruce Eckel), Стефан Мишель (Stéphane Micheloud), Тод Миль­
штейн (Todd Millstein), Дэвид Поллак (David Pollak), Филип Уодлер (Philip
Wadler) и Матиас Зенгер (Matthias Zenger). Спасибо также представителям
Silicon Valley Patterns group за их весьма полезный обзор. Это Дейв Астелс
(Dave Astels), Трейси Бялик (Tracy Bialik), Джон Брюер (John Brewer),
Эндрю Чейз (Andrew Chase), Брэдфорд Кросс (Bradford Cross), Рауль Дюк
(Raoul Duke), Джон П. Эйрих (John P. Eurich), Стивен Ганц (Steven Ganz),
Фил Гудвин (Phil Goodwin), Ральф Йочем (Ralph Jocham), Ян-Фа Ли (YanFa Li), Тао Ма (Tao Ma), Джеффри Миллер (Jeffery Miller), Суреш Пай
(Suresh Pai), Русс Руфер (Russ Rufer), Дэйв У. Смит (Dave W. Smith), Скотт
Торнквест (Scott Turnquest), Вальтер Ваннини (Walter Vannini), Дарлин
Уоллах (Darlene Wallach) и Джонатан Эндрю Уолтер (Jonathan Andrew
Wolter). Кроме того, хочется поблагодарить Дуэйна Джонсона (Dewayne
Johnson) и Кима Лиди (Kim Leedy) за помощь в художественном оформле­
нии обложки, а также Фрэнка Соммерса (Frank Sommers) — за работу над
алфавитным указателем.
Хотелось бы выразить особую благодарность и всем нашим читателям, при­
славшим комментарии. Они нам очень пригодились для повышения качества
книги. Мы не в состоянии опубликовать имена всех приславших комментарии,
но объявим имена тех читателей, кто прислал не менее пяти комментариев на
стадии eBook PrePrint™. Отсортируем их имена по убыванию количества ком­
ментариев. Наших благодарностей заслуживают Дэвид Бизак (David Biesack),
Дон Стефан (Donn Stephan), Матс Хенриксон (Mats Henricson), Роб Диккенс
(Rob Dickens), Блэр Захак (Blair Zajac), Тони Слоан (Tony Sloane), Найджел
Харрисон (Nigel Harrison), Хавьер Диас Сото (Javier Diaz Soto), Уильям Хе­
лан (William Heelan), Джастин Фурдер (Justin Forder), Грегор Перди (Gregor
Purdy), Колин Перкинс (Colin Perkins), Бьярте С. Карлсен (Bjarte S. Karlsen),
Эрвин Варга (Ervin Varga), Эрик Уиллигерс (Eric Willigers), Марк Хейс (Mark
Hayes), Мартин Элвин (Martin Elwin), Калум Маклин (Calum MacLean), Джо­

Благодарности   29

натан Уолтер (Jonathan Wolter), Лес Прушински (Les Pruszynski), Сет Тисуе
(Seth Tisue), Андрей Формига (Andrei Formiga), Дмитрий Григорьев (Dmitry
Grigoriev), Джордж Бергер (George Berger), Говард Ловетт (Howard Lovatt),
Джон П. Эйрих (John P. Eurich), Мариус Скуртеску (Marius Scurtescu),
Джефф Эрвин (Jeff Ervin), Джейми Уэбб (Jamie Webb), Курт Зольман (Kurt
Zoglmann), Дин Уэмплер (Dean Wampler), Николай Линдберг (Nikolaj
Lindberg), Питер Маклейн (Peter McLain), Аркадиуш Стрыйски (Arkadiusz
Stryjski), Шанки Сурана (Shanky Surana), Крейг Борделон (Craig Bordelon),
Александр Пэтри (Alexandre Patry), Филип Моэнс (Filip Moens), Фред Янон
(Fred Janon), Джефф Хеон (Jeff Heon), Борис Лорбир (Boris Lorbeer), Джим
Менард (Jim Menard), Тим Аццопарди (Tim Azzopardi), Томас Юнг (Thomas
Jung), Уолтер Чанг (Walter Chang), Йерун Дийкмейер (Jeroen Dijkmeijer),
Кейси Боумен (Casey Bowman), Мартин Смит (Martin Smith), Ричард Даллау­
эй (Richard Dallaway), Энтони Стаббс (Antony Stubbs), Ларс Вестергрен (Lars
Westergren), Маартен Хэйзвинкель (Maarten Hazewinkel), Мэтт Рассел (Matt
Russell), Ремигиус Михаловски (Remigiusz Michalowski), Андрей Толопко
(Andrew Tolopko), Кертис Стэнфорд (Curtis Stanford), Джошуа Каф (Joshua
Cough), Земен Денг (Zemian Deng), Кристофер Родригес Масиас (Christopher
Rodrigues Macias), Хуан Мигель Гарсия Лопес (Juan Miguel Garcia Lopez),
Мишель Шинц (Michel Schinz), Питер Мур (Peter Moore), Рэндольф Кал
(Randolph Kahle), Владимир Кельман (Vladimir Kelman), Даниэль Гронау
(Daniel Gronau), Дирк Детеринг (Dirk Detering), Хироаки Накамура (Hiroaki
Nakamura), Оле Хугаард (Ole Hougaard), Бхаскар Маддала (Bhaskar Maddala),
Дэвид Бернар (David Bernard), Дерек Махар (Derek Mahar), Джордж Кол­
лиас (George Kollias), Кристиан Нордал (Kristian Nordal), Нормен Мюллер
(Normen Mueller), Рафаэль Феррейра (Rafael Ferreira), Бинил Томас (Binil
Thomas), Джон Нильсон (John Nilsson), Хорхе Ортис (Jorge Ortiz), Маркус
Шульте (Marcus Schulte), Вадим Герасимов (Vadim Gerasimov), Кэмерон
Таггарт (Cameron Taggart), Джон-Андерс Тейген (Jon-Anders Teigen), Силь­
вестр Забала (Silvestre Zabala), Уилл Маккуин (Will McQueen) и Сэм Оуэн
(Sam Owen).
Хочется также сказать спасибо тем, кто отправил сообщения о замеченных
неточностях после публикации первых двух изданий. Это Феликс Зигрист
(Felix Siegrist), Лотар Мейер-Лербс (Lothar Meyer-Lerbs), Диетард Михаэ­
лис (Diethard Michaelis), Рошан Даврани (Roshan Dawrani), Донн Стефан
(Donn Stephan), Уильям Утер (William Uther), Франсиско Ревербель
(Francisco Reverbel), Джим Балтер (Jim Balter), Фрик де Брюйн (Freek de
Bruijn), Амброз Лэнг (Ambrose Laing), Сехар Прабхала (Sekhar Prabhala),
Левон Салдамли (Levon Saldamli), Эндрю Бурсавич (Andrew Bursavich),
Хьялмар Петерс (Hjalmar Peters), Томас Фер (Thomas Fehr), Ален О’Ди
(Alain O’Dea), Роб Диккенс (Rob Dickens), Тим Тейлор (Tim Taylor),

30   Благодарности

Кристиан Штернагель (Christian Sternagel), Мишель Паризьен (Michel
Parisien), Джоэл Нили (Joel Neely), Брайан Маккеон (Brian McKeon),
Томас Фер (Thomas Fehr), Джозеф Эллиотт (Joseph Elliott), Габриэль
да Силва Рибейро (Gabriel da Silva Ribeiro), Томас Фер (Thomas Fehr),
Пабло Рипольес (Pablo Ripolles), Дуглас Гейлор (Douglas Gaylor), Кевин
Сквайр (Kevin Squire), Гарри-Антон Талвик (Harry-Anton Talvik), Кри­
стофер Симпкинс (Christopher Simpkins), Мартин Витман-Функ (Martin
Witmann-Funk), Джим Балтер (Jim Balter), Питер Фостер (Peter Foster),
Крейг Бордолон (Craig Bordelon), Хайнц-Питер Гум (Heinz-Peter Gumm),
Питер Чапин (Peter Chapin), Кевин Райт (Kevin Wright), Анантан Сри­
нивасан (Ananthan Srinivasan), Омар Килани (Omar Kilani), Дон Стефан
(Donn Stephan), Гюнтер Ваффлер (Guenther Waffler).
Лекс хотел бы поблагодарить специалистов, среди которых Аарон Абрамс
(Aaron Abrams), Джейсон Адамс (Jason Adams), Генри и Эмили Крутчер
(Henry and Emily Crutcher), Джои Гибсон (Joey Gibson), Гунар Хиллерт
(Gunnar Hillert), Мэтью Линк (Matthew Link), Тоби Рейлтс (Toby Reyelts),
Джейсон Снейп (Jason Snape), Джон и Мелинда Уэзерс (John and Melinda
Weathers), и всех представителей Atlanta Scala Enthusiasts за множество по­
лезных обсуждений структуры языка, его математических основ и способов
представления языка Scala специалистам-практикам.
Особую благодарность хочется выразить Дэйву Брикчетти (Dave Briccetti)
и Адриану Мурсу (Adriaan Moors) за рецензирование третьего издания,
а также Маркони Ланна (Marconi Lanna) не только за рецензирование, но
и за мотивацию выпустить третье издание, которая возникла после разговора
о новинках, появившихся со времени выхода предыдущего издания.
Билл хотел бы поблагодарить нескольких специалистов за предоставленную
информацию и советы по изданию книги. Его благодарность заслужили Гэри
Корнелл (Gary Cornell), Грег Доенч (Greg Doench), Энди Хант (Andy Hunt),
Майк Леонард (Mike Leonard), Тайлер Ортман (Tyler Ortman), Билл Поллок
(Bill Pollock), Дейв Томас (Dave Thomas) и Адам Райт (Adam Wright). Билл
также хотел бы поблагодарить Дика Уолла (Dick Wall) за сотрудничество над
разработкой нашего курса Stairway to Scala, который большей частью осно­
вывался на материалах, вошедших в эту книгу. Наш многолетний опыт пре­
подавания курса Stairway to Scala помог повысить его качество. И наконец,
Билл хотел бы выразить благодарность Дарлин Грюндль (Darlene Gruendl)
и Саманте Вулф (Samantha Woolf) за помощь в завершении третьего издания.
Наконец, мы хотели бы поблагодарить Жюльена Ричарда-Фоя (Julien Ri­
chard-Foy) за работу над обновлением четвертого издания этой книги до
версии Scala 2.13, в частности за перепроектирование библиотеки коллекций.

Введение

Эта книга — руководство по Scala, созданное людьми, непосредственно за­
нимающимися разработкой данного языка программирования. Нашей целью
было научить вас всему, что необходимо для превращения в продуктивного
программиста на языке Scala. Все примеры в книге компилируются с по­
мощью Scala версии 3.0.0

Целевая аудитория
Книга в основном рассчитана на программистов, желающих научиться про­
граммировать на Scala. Если у вас есть желание создать свой следующий
проект на этом языке, то наша книга вам подходит. Кроме того, она должна
заинтересовать программистов, которые хотят расширить кругозор, изучив
новые концепции. Если вы, к примеру, программируете на Java, то эта книга
раскроет для вас множество концепций функционального программирования,
а также передовых идей из сферы объектно-ориентированного программиро­
вания. Мы уверены: изучение Scala и заложенных в этот язык идей поможет
вам повысить свой профессиональный уровень как программиста. Предпола­
гается, что вы уже владеете общими знаниями в области программирования.
Scala вполне подходит на роль первого изучаемого языка, однако это не та
книга, которая может использоваться для обучения программированию. В то
же время вам не нужно быть каким-то особенным знатоком языков програм­
мирования. Большинство людей использует Scala на платформе Java, однако
наша книга не предполагает, что вы тесно знакомы с языком Java. Но все же
мы ожидаем, что Java известен многим читателям, и поэтому иногда сравни­
ваем оба языка, чтобы помочь таким читателям понять разницу.

Как пользоваться книгой
Книгу рекомендуется читать в порядке следования глав, от начала до конца.
Мы очень старались в каждой главе вводить читателя в курс только одной

32   Введение

темы и объяснять новый материал лишь в понятиях из ранее рассмотренных
тем. Поэтому если перескочить вперед, чтобы поскорее в чем-то разобраться,
то можно встретить объяснения, в которых используются еще непонятные
концепции. Мы считаем, что получать знания в области программирования
на Scala лучше постепенно, читая главы в порядке их следования.
Встретив незнакомое понятие, можно обратиться к глоссарию. Многие чи­
татели бегло просматривают части книги, и это вполне нормально. Но при
встрече с непонятными терминами можно выяснить, что просмотр был
слишком поверхностным, и вернуться к справочным материалам.
Прочитав книгу, вы можете в дальнейшем использовать ее в качестве спра­
вочника по Scala. Конечно, существует официальная спецификация языка,
но в ней прослеживается стремление к точности в ущерб удобству чтения.
В нашем издании не охвачены абсолютно все подробности Scala, но его осо­
бенности изложены в нем вполне обстоятельно. Так что по мере освоения
программирования на этом языке издание вполне может стать доступным
справочником.

Как изучать Scala
Весьма обширные познания о Scala можно получить, просто прочитав книгу
от начала до конца. Но быстрее и основательнее освоить язык можно с по­
мощью ряда дополнительных действий.
Прежде всего можно воспользоваться множеством примеров программ,
включенных в эту книгу. Самостоятельно набирая их, вам придется вду­
мываться в каждую строку кода. Попытки его разнообразить позволят вам
глубже заинтересоваться изучаемой темой и убедиться в том, что вы дей­
ствительно поняли, как работает этот код.
Затем можно поучаствовать в работе множества онлайн-форумов. Это по­
зволит вам и многим другим приверженцам Scala помочь друг другу в его
освоении. Есть множество рассылок, дискуссионных форумов, чатов, викиисточников и несколько информационных каналов, которые посвящены
этому языку и содержат соответствующие публикации. Уделите время по­
иску источников информации, более всего отвечающих вашим запросам.
Вы будете гораздо быстрее решать мелкие проблемы, что позволит уделять
больше времени более серьезным и глубоким вопросам.
И наконец, получив при чтении книги достаточный объем знаний, присту­
пайте к разработке собственного проекта. Поработайте с нуля над созданием

Структура книги   33

какой-нибудь небольшой программы или разработайте дополнение к более
объемной. Вы не добьетесь быстрых результатов одним только чтением.

Условные обозначения
При первом упоминании какого-либо понятия или термина его название
дается курсивом. Для небольших встроенных в текст примеров кода, таких
как x + 1 , используется моноширинный шрифт. Большие примеры кода
представлены в виде отдельных блоков, для которых тоже используется
моноширинный шрифт:
def hello() =
println("Hello, world!")

Когда показывается работа с интерактивной оболочкой, ответы последней
выделяются шрифтом на сером фоне:
scala> 3 + 4
val res0: Int = 7

Структура книги
zzГлава 1 «Масштабируемый язык» представляет обзор структуры языка

Scala, а также ее логическое обоснование и историю.
zzГлава

2 «Первые шаги в Scala» показывает, как в языке выполняется
ряд основных задач программирования, не вдаваясь в подробности,
касающиеся особенностей работы механизмов языка. Цель этой гла­
вы — заставить ваши пальцы набирать и запускать код на Scala.

zzГлава

3 «Дальнейшие шаги в Scala» показывает ряд основных задач
программирования, помогающих ускорить освоение этого языка. Из­
учив данную главу, вы сможете использовать Scala для автоматизации
простых задач.

zzГлава

4 «Классы и объекты» закладывает начало углубленного рас­
смотрения языка Scala, приводит описание его основных объектноориентированных строительных блоков и указания по выполнению
компиляции и запуску приложений Scala.

zzГлава

5 «Основные типы и операции» охватывает основные типы
Scala, их литералы, операции, которые могут над ними проводиться,

34   Введение

вопросы работы уровней приоритета и ассоциативности и дает пред­
ставление об обогащающих оболочках.
zzГлава

6 «Функциональные объекты» углубляет представление об
объектно-ориентированных свойствах Scala, используя в качестве при­
мера функциональные (то есть неизменяемые) рациональные числа.

zzГлава 7 «Встроенные управляющие конструкции» показывает способы

использования таких конструкций Scala, как if, while, for, try и match.
zzГлава 8 «Функции и замыкания» углубленно рассматривает функции

как основные строительные блоки функциональных языков.
zzГлава 9 «Управляющие абстракции» показывает, как усовершенство­

вать основные управляющие конструкции Scala с помощью определе­
ния собственных управляющих абстракций.
zzГлава

10 «Композиция и наследование» рассматривает имеющуюся
в Scala дополнительную поддержку объектно-ориентированного про­
граммирования. Затрагиваемые темы не столь фундаментальны, как
те, что излагались в главе 4, но вопросы, которые в них рассматрива­
ются, часто встречаются на практике.

zzГлава

11 «Трейты» охватывает существующий в Scala механизм соз­
дания композиции примесей. Показана работа трейтов, описываются
примеры их наиболее частого использования, и объясняется, как
с помощью трейтов совершенствуется традиционное множественное
наследование.

zzГлава 12 «Пакеты, импорты и экспорты» рассматривает вопросы про­

граммирования в целом, включая высокоуровневые пакеты, инструк­
ции импортирования и модификаторы управления доступом, такие
как protected и private.
zzГлава

13 «Сопоставление с образцом» описывает двойные конструк­
ции, которые помогут вам при написании обычных, неинкапсулиро­
ванных структур данных. Классы регистра и сопоставление с образцом
особенно полезны для древовидных рекурсивных данных.

zzГлава 14 «Работа со списками» подробно рассматривает списки, кото­

рые, вероятно, можно отнести к самым востребованным структурам
данных в программах на Scala.
zzГлава

15 «Работа с другими коллекциями» показывает способы ис­
пользования основных коллекций Scala, таких как списки, массивы,
кортежи, множества и отображения.

Ресурсы   35

zzГлава 16 «Изменяемые объекты» объясняет суть изменяемых объектов

и синтаксиса для выражения этих объектов, обеспечиваемого Scala.
Глава завершается практическим примером моделирования дискрет­
ного события, в котором показан ряд изменяемых объектов в действии.
zzГлава

17 «Иерархия Scala» объясняет иерархию наследования языка
и рассматривает универсальные методы и низшие типы.

zzГлава

18 «Параметризация типов» объясняет некоторые методы со­
крытия информации, представленные в главе 13, на конкретном при­
мере: конструкции класса для чисто функциональных очередей. Глава
строится на описании вариации параметров типа и того, как она вза­
имодействует с сокрытием информации.

zzГлава

19 «Перечисления» вводит двойные конструкции, которые по­
могут вам при написании обычных, неинкапсулированных структур
данных.

zzГлава 20 «Абстрактные члены» дает описание всех видов абстрактных

членов, поддерживаемых Scala, — не только методов, но и полей и ти­
пов, которые можно объявлять абстрактными.
zzГлава

21 «Гивены» описывает функцию Scala, которая помогает вам
работать с контекстными параметрами функций. Передача всей кон­
текстной информации проста, но может потребовать большого коли­
чества шаблонов. Гивены позволят вам упростить эту задачу.

zzГлава

22 «Методы расширения» описывает механизм Scala, позволя­
ющий создать впечатление, что функция определена как метод в клас­
се, хотя на самом деле она определена вне класса.

zzГлава

23 «Классы типов» (которую еще предстоит написать). В этой
главе будет проиллюстрировано несколько примеров классов типов.

zzГлава 24 «Углубленное изучение коллекций» предлагает углубленный

обзор библиотеки коллекций.
zzГлава

25 «Утверждения и тесты» демонстрирует механизм утвер­
ждения Scala и дает обзор нескольких инструментов, доступных для
написания тестов на Scala, уделяя особое внимание ScalaTest.

Ресурсы
На https://www.scala-lang.org — официальном сайте Scala — вы найдете по­
следнюю версию Scala и ссылки на документацию и ресурсы сообщества.

36   Введение

Исходный код и дополнительные материалы к книге вы найдете по адресу
https://booksites.artima.com/programming_in_scala_5ed.

Исходный код
Исходный код, рассматриваемый в данной книге, выпущенный под откры­
той лицензией в виде ZIP-файла, можно найти на сайте книги: https://booksi­
tes.artima.com/programming_in_scala_5ed.

От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com
(издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию
о наших книгах.

1

Масштабируемый язык

Scala означает «масштабируемый язык» (от англ. scalable language). Это на­
звание он получил, поскольку был спроектирован так, чтобы расти вместе
с запросами своих пользователей. Язык Scala может решать широкий круг
задач программирования: от написания небольших скриптов до создания
больших систем1.
Scala легко освоить. Он работает на стандартных платформах Java и JavaScript
и без проблем взаимодействует с библиотеками обеих платформ. Это довольно
хороший язык для написания скриптов, объединяющих существующие би­
блиотеки. Но он может еще больше проявить себя при построении больших
систем и фреймворков из компонентов многократного использования.
С технической точки зрения Scala — смесь объектно-ориентированной
и функциональной концепций программирования в статически типизиро­
ванном языке. Подобный сплав проявляется во многих аспектах Scala — он,
вероятно, может считаться более всеобъемлющим, чем другие широко ис­
пользуемые языки. Когда дело доходит до масштабируемости, два стиля про­
граммирования дополняют друг друга. Используемые в Scala конструкции
функционального программирования упрощают быстрое создание инте­
ресных компонентов из простых частей. Объектно-ориентированные кон­
струкции же облегчают структурирование больших систем и их адаптацию
к новым требованиям. Сочетание двух стилей в Scala позволяет создавать
новые виды шаблонов программирования и абстракций компонентов. Оно
также способствует выработке понятного и лаконичного стиля программиро­
вания. И благодаря такой гибкости языка программирование на Scala может
принести массу удовольствия.
1

Scala произносится как «скала».

38   Глава 1



Масштабируемый язык

В этой вступительной главе мы отвечаем на вопрос «Почему именно Scala?».
Мы даем общий обзор структуры Scala и ее обоснование. Прочитав главу,
вы получите базовое представление о том, что такое Scala и с какого рода
задачами он поможет справиться. Книга представляет собой руководство по
языку Scala, однако данную главу нельзя считать частью этого руководства.
И если вам не терпится приступить к написанию кода на Scala, то можете
сразу перейти к изу­чению главы 2.

1.1. Язык, который растет вместе с вами
Программы различных размеров требуют, как правило, использования
разных программных конструкций. Рассмотрим, к примеру, следующую
небольшую программу на Scala:
var capital = Map("US" –> "Washington", "France" –> "Paris")
capital += ("Japan" –> "Tokyo")
println(capital("France"))

Эта программа устанавливает отображение стран на их столицы, модифи­
цирует отображение, добавляя новую конструкцию ("Japan" –> "Tokyo"),
и выводит название столицы, связанное со страной France1. В этом примере
используется настолько высокоуровневая система записи, что она не загро­
мождена ненужными точками с запятыми и сигнатурами типов. И действи­
тельно возникает ощущение использования современного языка скриптов
наподобие Perl, Python или Ruby. Одна из общих характеристик этих языков,
применимая к данному примеру, — поддержка всеми ими в синтаксисе языка
конструкции ассоциативного отображения.
Ассоциативные отображения очень полезны, поскольку помогают поддер­
живать понятность и краткость программ, но порой вам может не подойти их
философия «на все случаи жизни», поскольку вам в своей программе нужно
управлять свойствами отображений более тонко. При необходимости Scala
обеспечивает точное управление, поскольку отображения в нем не являются
синтаксисом языка. Это библиотечные абстракции, которые можно расши­
рять и приспосабливать под свои нужды.
В показанной ранее программе вы получите исходную реализацию ото­
бражения Map, но ее можно будет без особого труда изменить. К примеру,
можно указать конкретную реализацию, такую как HashMap или TreeMap,
1

Пожалуйста, не сердитесь на нас, если не сможете разобраться во всех тонкостях
этой программы. Объяснения будут даны в двух следующих главах.

1.1. Язык, который растет вместе с вами   39

или с помощью модуля параллельных коллекций Scala вызвать метод par
для получения отображения ParMap, операции в котором выполняются па­
раллельно. Можно указать для отображения значение по умолчанию или
переопределить любой другой метод созданного вами отображения. Во всех
случаях для отображений вполне пригоден такой же простой синтаксис до­
ступа, как и в приведенном примере.
В нем показано, что Scala может обеспечить вам как удобство, так и гибкость.
Язык содержит набор удобных конструкций, которые помогают быстро на­
чать работу и позволяют программировать в приятном лаконичном стиле.
В то же время вы всегда сможете перекроить программу под свои требования,
поскольку все в ней основано на библиотечных модулях, которые можно
выбрать и приспособить под свои нужды.

Растут новые типы
Эрик Рэймонд (Eric Raymond) в качестве двух метафор разработки про­
граммных продуктов ввел собор и базар [Ray99]. Под собором понимается
почти идеальная разработка, создание которой требует много времени. После
сборки она долго остается неизменной. Разработчики же базара, напротив,
что-то адаптируют и дополняют каждый день. В книге Рэймонда базар —
метафора, описывающая разработку ПО с открытым кодом. Гай Стил (Guy
Steele) отметил в докладе о «растущем языке», что аналогичное различие
можно применить к структуре языка программирования [Ste99]. Scala больше
похож на базар, чем на собор, в том смысле, что спроектирован с расчетом на
расширение и адаптацию его теми, кто на нем программирует. Вместо того
чтобы предоставлять все конструкции, которые только могут пригодиться
в одном всеобъемлющем языке, Scala дает вам инструменты для создания
таких конструкций.
Рассмотрим пример. Многие приложения нуждаются в целочисленном типе,
который при выполнении арифметических операций может становиться
произвольно большим без переполнения или циклического перехода в на­
чало. В Scala такой тип определяется в библиотеке класса scala.math.BigInt.
Определение использующего этот тип метода, который вычисляет факториал
переданного ему целочисленного значения, имеет следующий вид1:
def factorial(x: BigInt): BigInt =
if x == 0 then 1 else x * factorial(x - 1)
1

factorial(x), или x! в математической записи — результат вычисления 1 * 2 * ∙∙∙ *
* x, где для 0! определено значение 1.

40   Глава 1



Масштабируемый язык

Теперь, вызвав factorial(30), вы получите:
265252859812191058636308480000000

Тип BigInt похож на встроенный, поскольку со значениями этого типа
можно использовать целочисленные литералы и операторы наподобие * и –.
Тем не менее это просто класс, определение которого задано в стандартной
библиотеке Scala1. Если бы класса не было, то любой программист на Scala
мог бы запросто написать его реализацию, например создав оболочку для
имеющегося в языке Java класса java.math.BigInteger (фактически именно
так и реализован класс BigInt в Scala).
Конечно, класс Java можно использовать напрямую. Но результат будет не
столь приятным: хоть Java и позволяет вам создавать новые типы, они не
производят впечатление получающих естественную поддержку языка:
import java.math.BigInteger
def factorial(x: BigInteger): BigInteger =
if x == BigInteger.ZERO then
BigInteger.ONE
else
x.multiply(factorial(x.subtract(BigInteger.ONE)))

Тип BigInt — один из многих других числовых типов: больших десятичных
чисел, комплексных и рациональных чисел, доверительных интервалов,
полиномов, и данный список можно продолжить. В некоторых языках
программирования часть этих типов реализуется естественным образом.
Например, в Lisp, Haskell и Python есть большие целые числа, в Fortran
и Python — комплексные. Но любой язык, в котором пытаются одновре­
менно реализовать все эти абстракции, разрастается до таких размеров, что
становится неуправляемым. Более того, даже существуй подобный язык,
нашлись бы приложения, требующие других числовых типов, которые все
равно не были бы представлены. Следовательно, подход, при котором пред­
принимается попытка реализовать все в одном языке, не позволяет получить
хорошую масштабируемость. Язык Scala, напротив, дает пользователям воз­
можность наращивать и адаптировать его в нужных направлениях. Он делает
это с помощью определения простых в использовании библиотек, которые
производят впечатление средств, естественно реализованных в языке.
1

Scala поставляется со стандартной библиотекой, часть которой будет рассмотре­
на в кни­ге. За дополнительной информацией можно обратиться к имеющейся
в библиотеке документации Scaladoc, доступной в дистрибутиве и в интернете
по адресу www.scala-lang.org.

1.2. Почему язык Scala масштабируемый?   41

Растут новые управляющие конструкции
Такая расширяемость иллюстрируется стилем AnyFunSuite ScalaTest, попу­
лярной библиотеки тестирования для Scala. В качестве примера приведем
простой тестовый класс, содержащий два теста:
class SetSpec extends AnyFunSuite:
test("An empty Set should have size 0") {
assert(Set.empty.size == 0)
}
test("Invoking head on an empty Set should fail") {
assertThrows[NoSuchElementException] {
Set.empty.head
}
}

Мы не ожидаем, что вы сейчас полностью поймете пример AnyFunSuite. Ско­
рее, что важно в этом примере для темы масштабируемости, так это то, что ни
тестовая конструкция, ни синтаксис assertThrows не являются встроенными
операциями в Scala. Хотя обе они могут выглядеть и действовать очень по­
хоже на встроенные управляющие конструкции, на самом деле они являются
методами, определенными в библиотеке ScalaTest. Обе эти конструкции
полностью независимы от языка программирования Scala.
Этот пример показывает, что вы можете «развивать» язык Scala в новых на­
правлениях, даже таких специализированных, как тестирование программ­
ного обеспечения. Конечно, для этого нужны опытные архитекторы и про­
граммисты. Но важно то, что это осуществимо — вы можете разрабатывать
и реализовывать абстракции в Scala, которые адресованы радикально новым
доменам приложений, но при этом ощущать поддержку родного языка при
использовании.

1.2. Почему язык Scala масштабируемый?
На возможность масштабирования влияет множество факторов, от осо­
бенностей синтаксиса до структуры абстрактных компонентов. Но если
бы потребовалось назвать всего один аспект Scala, который способствует
масштабируемости, то мы бы выбрали присущее этому языку сочета­
ние объектно-ориентированного и функционального программирования
(мы ­немного слукавили, на самом деле это два аспекта, но они взаимо­
связаны).

42   Глава 1



Масштабируемый язык

Scala в объединении объектно-ориентированного и функционального про­
граммирования в однородную структуру языка пошел дальше всех осталь­
ных широко известных языков. Например, там, где в других языках объекты
и функции — два разных понятия, в Scala функция по смыслу является
объектом. Функциональные типы — это классы, которые могут наследо­
ваться подклассами. Эти особенности могут показаться не более чем тео­
ретическими, но имеют весьма серьезные последствия для возможностей
масштабирования. Фактически ранее упомянутое понятие актора не может
быть реализовано без этой унификации функций и объектов. Здесь мы рас­
смотрим возможные в Scala способы смешивания объектно-ориентирован­
ной и функциональной концепций.

Scala —объектно-ориентированный язык
Развитие объектно-ориентированного программирования шло весьма успеш­
но. Появившись в языке Simula в середине 1960-х годов и Smalltalk в 1970-х,
оно теперь доступно в подавляющем большинстве языков. В некоторых
областях все полностью захвачено объектами. Точного определения «объ­
ектной ориентированности» нет, однако объекты явно чем-то привлекают
программистов.
В принципе, мотивация для применения объектно-ориентированного про­
граммирования очень проста: все, за исключением самых простых программ,
нуждается в определенной структуре. Наиболее понятный путь достижения
желаемого результата заключается в помещении данных и операций в свое­
образные контейнеры. Основной замысел объектно-ориентированного
программирования состоит в придании этим контейнерам полной универ­
сальности, чтобы в них могли содержаться не только операции, но и данные
и чтобы сами они также были элементами, которые могли бы храниться
в других контейнерах или передаваться операциям в качестве параметров.
Подобные контейнеры называются объектами. Алан Кей (Alan Kay), изобре­
татель языка Smalltalk, заметил, что таким образом простейший объект имеет
принцип построения, аналогичный полноценному компьютеру: под форма­
лизованным интерфейсом данные в нем сочетаются с операциями [Kay96].
То есть объекты имеют непосредственное отношение к масштабируемости
языка: одни и те же технологии применяются к построению как малых, так
и больших программ.
Хотя долгое время объектно-ориентированное программирование преобла­
дало, немногие языки стали последователями Smalltalk по части внедрения
этого принципа построения в свое логическое решение. Например, множе­

1.2. Почему язык Scala масштабируемый?   43

ство языков допускает использование элементов, не являющихся объекта­
ми, — можно вспомнить имеющиеся в языке Java значения примитивных
типов. Или же в них допускается применение статических полей и методов,
не входящих в какой-либо объект. Эти отклонения от чистой идеи объектноориентированного программирования на первый взгляд выглядят вполне
безобидными, но имеют досадную тенденцию к усложнению и ограничению
масштабирования.
В отличие от этого Scala — объектно-ориентированный язык в чистом виде:
каждое значение является объектом и каждая операция — вызовом метода.
Например, когда в Scala речь заходит о вычислении 1 + 2, фактически вызы­
вается метод по имени +, который определен в классе Int. Можно определять
методы с именами, похожими на операторы, а клиенты вашего API смогут
с помощью этих методов записать операторы.
Когда речь заходит о составлении объектов, Scala проявляется как более со­
вершенный язык по сравнению с большинством других. В качестве примера
приведем имеющиеся в Scala трейты. Они подобны интерфейсам в Java, но
могут содержать также реализации методов и даже поля1. Объекты создаются
путем композиции примесей, при котором к членам класса добавляются чле­
ны нескольких трейтов. Таким образом, различные аспекты классов могут
быть инкапсулированы в разных трейтах. Это выглядит как множественное
наследование, но есть разница в конкретных деталях. В отличие от класса
трейт может добавить в суперкласс новые функциональные возможности.
Это придает трейтам более высокую степень подключаемости по сравнению
с классами. В частности, благодаря этому удается избежать возникновения
присущих множественному наследованию классических проблем «ром­
бовидного» наследования, которые возникают, когда один и тот же класс
наследуется по нескольким различным путям.

Scala — функциональный язык
Наряду с тем, что Scala является чистым объектно-ориентированным язы­
ком, его можно назвать и полноценным функциональным языком. Идеи
функционального программирования старше электронных вычислительных
систем. Их основы были заложены в лямбда-исчислении Алонзо Черча
(Alonzo Church), разработанном в 1930-е годы. Первым языком функцио­
нального программирования был Lisp, появление которого датируется
1

Начиная с Java 8, у интерфейсов могут быть реализации методов по умолчанию,
но они не предлагают всех тех возможностей, которые есть у трейтов языка Scala.

44   Глава 1



Масштабируемый язык

концом 1950-х. К другим популярным функциональным языкам относятся
Scheme, SML, Erlang, Haskell, OCaml и F#. Долгое время функциональное
программирование играло второстепенные роли — будучи популярным
в научных кругах, оно не столь широко использовалось в промышленности.
Но в последние годы интерес к его языкам и технологиям растет.
Функциональное программирование базируется на двух основных идеях.
Первая заключается в том, что функции являются значениями первого
класса. В функциональных языках функция есть значение, имеющее такой
же статус, как целое число или строка. Функции можно передавать в каче­
стве аргументов другим функциям, возвращать их в качестве результатов
из других функций или сохранять в переменных. Вдобавок функцию мож­
но определять внутри другой функции точно так же, как это делается при
определении внутри функции целочисленного значения. И функции можно
определять, не присваивая им имен, добавляя в код функциональные лите­
ралы с такой же легкостью, как и целочисленные, наподобие 42.
Функции как значения первого класса — удобное средство абстрагирования,
касающееся операций и создания новых управляющих конструкций. Эта
универсальность функций обеспечивает более высокую степень выразитель­
ности, что зачастую приводит к созданию весьма разборчивых и кратких
программ. Она также играет важную роль в обеспечении масштабируемости.
В качестве примера библиотека тестирования ScalaTest предлагает кон­
струкцию eventually, получающую функцию в качестве аргумента. Данная
конструкция используется следующим образом:
val xs = 1 to 3
val it = xs.iterator
eventually { it.next() shouldBe 3 }

Код внутри eventually, являющийся утверждением, it.next() shouldBe 3,
включает в себя функцию, передаваемую невыполненной в метод eventually.
Через настраиваемый период времени eventually станет неоднократно
выполнять функцию до тех пор, пока утверждение не будет успешно под­
тверждено.
Вторая основная идея функционального программирования заключается
в том, что операции программы должны преобразовать входные значения
в выходные, а не изменять данные на месте. Чтобы понять разницу, рас­
смотрим реализацию строк в Ruby и Java. В Ruby строка является масси­
вом символов. Символы в строке могут быть изменены по отдельности.
Например, внутри одного и того же строкового объекта символ точки
с запятой в строке можно заменить точкой. А в Java и Scala строка — по­
следовательность символов в математическом смысле. Замена символа

1.3. Почему именно Scala   45

в строке с использованием выражения вида s.replace(';', '.') приводит
к возникновению нового строкового объекта, отличающегося от s. То же
самое можно сказать по-другому: в Java строки неизменяемые, а в Ruby —
изменяемые. То есть, рассматривая только строки, можно прийти к выводу,
что Java — функциональный язык, а Ruby — нет. Неизменяемая структура
данных — один из краеугольных камней функционального программирова­
ния. В библиотеках Scala в качестве надстроек над соответствующими API
Java определяется также множество других неизменяемых типов данных.
Например, в Scala имеются неизменяемые списки, кортежи, отображения
и множества.
Еще один способ утверждения второй идеи функционального программи­
рования заключается в том, что у методов не должно быть никаких побочных эффектов. Они должны обмениваться данными со своим окружением
только путем получения аргументов и возвращения результатов. Например,
под это описание подпадает метод replace, принадлежащий Java-классу
String . Он получает строку и два символа и выдает новую строку, где
все появления одного символа заменены появлениями второго. Других
эффектов от вызова replace нет. Методы, подобные replace, называются
ссылочно прозрачными. Это значит, что для любого заданного ввода вызов
функции можно заменить его результатом, при этом семантика программы
остается неизменной.
Функциональные языки заставляют применять неизменяемые структуры
данных и ссылочно прозрачные методы. В некоторых функциональных
языках это выражено в виде категоричных требований. Scala же дает воз­
можность выбрать. При желании можно писать программы в императивном
стиле — так называется программирование с изменяемыми данными и по­
бочными эффектами. Но при необходимости в большинстве случаев Scala
позволяет с легкостью избежать использования императивных конструкций
благодаря существованию хороших функциональных альтернатив.

1.3. Почему именно Scala
Подойдет ли вам язык Scala? Разбираться и принимать решение придется
самостоятельно. Мы считаем, что, помимо хорошей масштабируемости,
существует еще множество причин, по которым вам может понравиться про­
граммирование на Scala. В этом разделе будут рассмотрены четыре наиболее
важных аспекта: совместимость, лаконичность, абстракции высокого уровня
и расширенная статическая типизация.

46   Глава 1



Масштабируемый язык

Scala — совместимый язык
Scala не требует резко отходить от платформы Java, чтобы опередить на шаг
этот язык. Scala позволяет повышать ценность уже существующего кода,
то есть опираться на то, что у вас уже есть, поскольку он был разработан для
достижения беспрепятственной совместимости с Java1. Программы на Scala
компилируются в байт-коды виртуальной машины Java (JVM). Произво­
дительность при выполнении этих кодов находится на одном уровне с про­
изводительностью программ на Java. Код Scala может вызывать методы Java,
обращаться к полям этого языка, поддерживать наследование от его классов
и реализовывать его интерфейсы. Для всего перечисленного не требуются
ни специальный синтаксис, ни явные описания интерфейса, ни какой-либо
связующий код. По сути, весь код Scala интенсивно использует библиотеки
Java, зачастую даже без ведома программистов.
Еще один показатель полной совместимости — интенсивное заимствование
в Scala типов данных Java. Данные типа Int в Scala представлены в виде
имеющегося в Java примитивного целочисленного типа int, соответственно
Float представлен как float, Boolean — как boolean и т. д. Массивы Scala
отображаются на массивы Java. В Scala из Java позаимствованы и многие
стандартные библиотечные типы. Например, тип строкового литерала "abc"
в Scala фактически представлен классом java.lang.String, а исключение
должно быть подклассом java.lang.Throwable.
Java-типы в Scala не только заимствованы, но и «принаряжены» для прида­
ния им привлекательности. Например, строки в Scala поддерживают такие
методы, как toInt или toFloat, которые преобразуют строки в целое число
или число с плавающей точкой. То есть вместо Integer.parseInt(str) вы
можете написать str.toInt. Как такое возможно без нарушения совмести­
мости? Класс String в Java определенно не имеет метода toInt! Фактически
у Scala есть очень общее решение для устранения этого противоречия между
передовой разработкой и функциональной совместимостью2. Scala позволяет
определять многофункцио­нальные расширения, которые всегда применя­
ются при выборе несуществующих элементов. В рассматриваемом случае
при поиске метода toInt для работы со строковым значением компилятор
1

2

Изначально существовала реализация Scala, запускаемая на платформе .NET, но
она больше не используется. В последнее время все большую популярность на­
бирает реализация Scala под названием Scala.js, запускаемая на JavaScript.
В версии 3.0.0 стандартные расширения реализованы посредством неявных пре­
образований. В последующих версиях Scala они будут заменены методами расши­
рения.

1.3. Почему именно Scala   47

Scala не найдет такого элемента в классе String. Однако он найдет неявное
преобразование, превращающее Java-класс String в экземпляр Scala-класса
StringOps, в котором такой элемент определен. Затем преобразование будет
автоматически применено, прежде чем будет выполнена операция toInt.
Код Scala также может быть вызван из кода Java. Иногда при этом следует
учитывать некоторые нюансы. Scala — более утонченный язык, чем Java, по­
этому некоторые расширенные функции Scala должны быть закодированы,
прежде чем они смогут быть отображены на Java.

Scala — лаконичный язык
Программы на Scala, как правило, отличаются краткостью. Программисты,
работающие с данным языком, отмечают сокращение количества строк почти
на порядок по сравнению с Java. Но это можно считать крайним случаем. Бо­
лее консервативные оценки свидетельствуют о том, что обычная программа
на Scala должна умещаться в половину тех строк, которые используются
для аналогичной программы на Java. Меньшее количество строк означает
не только сокращение объема набираемого текста, но и экономию сил при
чтении и осмыслении программ, а также уменьшение количества возможных
недочетов. Свой вклад в сокращение количества строк кода вносят сразу
несколько факторов.
В синтаксисе Scala не используются некоторые шаблонные элементы, отя­
гощающие программы на Java. Например, в Scala не обязательно применять
точки с запятыми. Есть и несколько других областей, где синтаксис Scala
менее зашумлен. В качестве примера можно сравнить, как записывается
код классов и конструкторов в Java и Scala. В Java класс с конструктором
зачастую выглядит следующим образом:
class MyClass { // Java
private int index;
private String name;
public MyClass(int index, String name) {
this.index = index;
this.name = name;
}
}

А в Scala, скорее всего, будет использована такая запись:
class MyClass(index: Int, name: String)

48   Глава 1



Масштабируемый язык

Получив указанный код, компилятор Scala создаст класс с двумя приват­
ными переменными экземпляра (типа Int по имени index и типа String по
имени name) и конструктор, который получает исходные значения для этих
переменных в виде параметров. Код данного конструктора проинициализи­
рует две переменные экземпляра значениями, переданными в качестве па­
раметров. Короче говоря, в итоге вы получите ту же функциональность, что
и у более многословной версии кода на Java1. Класс в Scala быстрее пишется
и проще читается, а еще — и это наиболее важно — допустить ошибку при его
создании значительно труднее, чем при создании класса в Java.
Еще один фактор, способствующий лаконичности, — используемый в Scala
вывод типов. Повторяющуюся информацию о типе можно отбросить, и тогда
программы избавятся от лишнего и их легче будет читать.
Но, вероятно, наиболее важный аспект сокращения объема кода — наличие
кода, не требующего внесения в программу, поскольку это уже сделано в би­
блиотеке. Scala предоставляет вам множество инструментальных средств
для определения эффективных библиотек, позволяющих выявить и вынести
за скобки общее поведение. Например, различные аспекты библиотечных
классов можно выделить в трейты, которые затем можно перемешивать про­
извольным образом. Или же библиотечные методы могут быть параметризо­
ваны с помощью операций, позволяя вам определять конструкции, которые,
по сути, являются вашими собственными управляющими конструкциями.
Собранные вместе, эти конструкции позволяют определять библиотеки, со­
четающие в себе высокоуровневый характер и гибкость.

Scala — высокоуровневый язык
Программисты постоянно борются со сложностью. Для продуктивного про­
граммирования нужно понимать код, над которым вы работаете. Чрезмерно
сложный код был причиной краха многих программных проектов. К сожа­
лению, важные программные продукты обычно бывают весьма сложными.
Избежать сложности невозможно, но ею можно управлять.
Scala помогает управлять сложностью, позволяя повышать уровень абстрак­
ции в разрабатываемых и используемых интерфейсах. Представим, к при­
меру, что есть переменная name, имеющая тип String, и нужно определить,

1

Единственное отличие заключается в том, что переменные экземпляра, полученные
в случае применения Scala, будут финальными (final). Как сделать их не финаль­
ными, рассказывается в разделе 10.6.

1.3. Почему именно Scala   49

наличествует ли в этой строковой переменной символ в верхнем регистре.
До выхода Java 8 приходилось создавать следующий цикл:
boolean nameHasUpperCase = false; // Java
for (int i = 0; i < name.length(); ++i) {
if (Character.isUpperCase(name.charAt(i))) {
nameHasUpperCase = true;
break;
}
}

А в Scala можно написать такой код:
val nameHasUpperCase = name.exists(_.isUpper)

Код Java считает строки низкоуровневыми элементами, требующими по­
символьного перебора в цикле. Код Scala рассматривает те же самые строки
как высокоуровневые последовательности символов, в отношении которых
можно применять запросы с предикатами. Несомненно, код Scala намного
короче и — для натренированного глаза — более понятен, чем код Java.
Следовательно, код Scala значительно меньше влияет на общую сложность
приложения. Кроме того, уменьшается вероятность допустить ошибку.
Предикат _.isUpper — пример используемого в Scala функционального ли­
терала1. В нем дается описание функции, которая получает аргумент в виде
символа (представленного знаком подчеркивания) и проверяет, не является
ли этот символ буквой в верхнем регистре2.
В Java 8 появилась поддержка лямбда-выражений и потоков (streams), по­
зволяющая выполнять подобные операции на Java. Вот как это могло бы
выглядеть:
boolean nameHasUpperCase = // Java 8 или выше
name.chars().anyMatch(
(int ch) –> Character.isUpperCase((char) ch)
);

Несмотря на существенное улучшение по сравнению с более ранними версия­
ми Java, код Java 8 все же более многословен, чем его эквивалент на языке
Scala. Излишняя тяжеловесность кода Java, а также давняя традиция исполь­
зования в этом языке циклов может натолкнуть многих Java-программистов
1

2

Функциональный литерал может называться предикатом, если результирующим
типом будет Boolean.
Такое использование символа подчеркивания в качестве заместителя для аргумен­
тов рассматривается в разделе 8.5.

50   Глава 1



Масштабируемый язык

на мысль о необходимости новых методов, подобных exists, позволяющих
просто переписать циклы и смириться с растущей сложностью кода.
В то же время функциональные литералы в Scala действительно восприни­
маются довольно легко и задействуются очень часто. По мере углубления
знакомства со Scala перед вами будет открываться все больше и больше воз­
можностей для определения и использования собственных управля­ющих
абстракций. Вы поймете, что это поможет избежать повторений в коде, со­
храняя лаконичность и чистоту программ.

Scala — статически типизированный язык
Системы со статической типизацией классифицируют переменные и выра­
жения в соответствии с видом хранящихся и вычисляемых значений. Scala
выделяется как язык своей совершенной системой статической типизации.
Обладая системой вложенных типов классов, во многом похожей на име­
ющуюся в Java, этот язык позволяет вам проводить параметризацию типов
с помощью средств обобщенного программирования, комбинировать типы
с использованием пересечений и скрывать особенности типов, применяя
абстрактные типы1. Так формируется прочный фундамент для создания
собственных типов, который дает возможность разрабатывать безопасные
и в то же время гибкие в использовании интерфейсы.
Если вам нравятся динамические языки, такие как Perl, Python, Ruby или
Groovy, то вы можете посчитать немного странным факт, что система ста­
тических типов в Scala упоминается как одна из его сильных сторон. Ведь
отсутствие такой системы часто называют основным преимуществом дина­
мических языков. Наиболее часто, говоря о ее недостатках, приводят такие
аргументы, как присущая программам многословность, воспрепятствование
свободному самовыражению программистов и невозможность применения
конкретных шаблонов динамических изменений программных систем.
Но зачастую эти аргументы направлены не против идеи статических типов
в целом, а против конкретных систем типов, воспринимаемых как слишком
многословные или недостаточно гибкие. Например, Алан Кей, автор языка
Smalltalk, однажды заметил: «Я не против типов, но не знаю ни одной бес­
проблемной системы типов. Так что мне все еще нравится динамическая
типизация»2.
1

2

Обобщенные типы рассматриваются в главе 18, пересечения (например,
A с B с C) — в разделе 17.5, а абстрактные типы — в главе 20.
Kay A. C. Электронное письмо о значении объектно-ориентированного програм­
мирования [Kay03].

1.3. Почему именно Scala   51

В этой книге мы надеемся убедить вас в том, что система типов в Scala далека
от проблемной. На самом деле она вполне изящно справляется с двумя обыч­
ными опасениями, связываемыми со статической типизацией: многословия
удается избежать за счет логического вывода типов, а гибкость достигается
благодаря сопоставлению с образцом и ряду новых способов записи и со­
ставления типов. По мере устранения этих препятствий к классическим
преимуществам систем статических типов начинают относиться намного
более благосклонно. Среди наиболее важных преимуществ можно назвать
верифицируемые свойства программных абстракций, безопасный рефакто­
ринг и более качественное документирование.
Верифицируемые свойства. Системы статических типов способны под­
тверждать отсутствие конкретных ошибок, выявляемых в ходе выполнения
программы. Это могут быть следующие правила: булевы значения никогда
не складываются с целыми числами; приватные переменные недоступны за
пределами своего класса; функции применяются к надлежащему количеству
аргументов; в множество строк можно добавлять только строки.
Существующие в настоящее время системы статических типов не выявляют
ошибки других видов. Например, обычно они не обнаруживают бесконечные
функции, нарушение границ массивов или деление на ноль. Вдобавок эти
системы не смогут определить несоответствие вашей программы ее специ­
фикации (при наличии таковой!). Поэтому некоторые отказываются от них,
считая не слишком полезными. Аргументация такова: если эти системы
могут выявлять только простые ошибки, а модульные тесты обеспечивают
более широкий охват, то зачем вообще связываться со статическими типами?
Мы считаем, что в этих аргументах упущено главное. Система статических
типов, конечно же, не может заменить собой модульное тестирование, од­
нако может сократить количество необходимых модульных тестов, выявляя
некие свойства, которые в противном случае нужно было бы протестировать.
А модульное тестирование не способно заменить статическую типизацию.
Ведь Эдсгер Дейкстра (Edsger Dijkstra) сказал, что тестирование позволяет
убедиться лишь в наличии ошибок, но не в их отсутствии [Dij70]. Гарантии,
которые обеспечиваются статической типизацией, могут быть простыми,
но это реальные гарантии, не способные обеспечить никакие объемы тести­
рования.
Безопасный рефакторинг. Системы статических типов дают гарантии,
позволяющие вам вносить изменения в основной код, будучи совершенно
уверенными в благополучном исходе этого действия. Рассмотрим, к при­
меру, рефакторинг, при котором к методу нужно добавить еще один пара­
метр. В статически типизированном языке вы можете внести изменения,

52   Глава 1



Масштабируемый язык

­ ерекомпилировать систему и просто исправить те строки, которые вызовут
п
ошибку типа. Сделав это, вы будете пребывать в уверенности, что были
найдены все места, требовавшие изменений. То же самое справедливо для
другого простого рефакторинга, например изменения имени метода или
перемещения метода из одного класса в другой. Во всех случаях проверка
статического типа позволяет быть вполне уверенными в том, что работо­
способность новой системы осталась на уровне работоспособности старой.
Документирование. Статические типы — документация программы, про­
веряемой компилятором на корректность. В отличие от обычного коммен­
тария, аннотация типа никогда не станет устаревшей (по крайней мере, если
содержащий ее исходный файл недавно успешно прошел компиляцию).
Более того, компиляторы и интегрированные среды разработки (integrated
development environments, IDE) могут использовать аннотации для выдачи
более качественной контекстной справки. Например, IDE может вывести на
экран все элементы, доступные для выбора, путем определения статическо­
го типа выражения, которое выбрано, и дать возможность просмотреть все
элементы этого типа.
Хотя статические типы в целом полезны для документирования програм­
мы, иногда они могут вызывать раздражение тем, что засоряют ее. Обычно
полезным считается документирование тех сведений, которые читателям
программы самостоятельно извлечь довольно трудно. Полезно знать, что
в методе, определенном так:
def f(x: String) = ...

аргументы метода f должны принадлежать типу String. В то же время может
вызвать раздражение по крайней мере одна из двух аннотаций в следующем
примере:
val x: HashMap[Int, String] = new HashMap[Int, String]()

Понятно, что было бы достаточно показать отношение x к типу HashMap с Intтипами в качестве ключей и String-типами в качестве значений только один
раз, дважды повторять одно и то же нет смысла.
В Scala имеется весьма сложная система логического вывода типов, позво­
ляющая опускать почти всю информацию о типах, которая обычно вызывает
раздражение. В предыдущем примере вполне работоспособны и две менее
раздражающие альтернативы:
val x = new HashMap[Int, String]()
val x: Map[Int, String] = new HashMap()

1.4. Истоки Scala   53

Вывод типа в Scala может заходить довольно далеко. Фактически пользова­
тельский код нередко вообще обходится без явного задания типов. Поэтому
программы на Scala часто выглядят похожими на программы, написанные
на динамически типизированных языках скриптов. Это, в частности, спра­
ведливо для прикладного клиентского кода, который склеивается из заранее
написанных библиотечных компонентов. Но для них самих это менее харак­
терно, поскольку в них зачастую применяются довольно сложные типы, не
допускающие гибкого использования таких схем. И это вполне естественно.
Ведь сигнатуры типов элементов, составляющих интерфейс повторно исполь­
зуемых компонентов, должны задаваться в явном виде, поскольку составляют
существенную часть соглашения между компонентом и его клиентами.

1.4. Истоки Scala
На идею создания Scala повлияли многие языки программирования и идеи,
выработанные на основе исследований таких языков. Фактически обновле­
ния в Scala незначительны — большинство характерных особенностей языка
уже применялось в том или ином виде в других языках программирования.
Инновации в Scala появляются в основном из того, как его конструкции
сводятся воедино. В этом разделе будут перечислены основные факторы,
оказавшие влияние на структуру языка Scala. Перечень не может быть ис­
черпывающим, поскольку в дизайне языков программирования так много
толковых идей, что перечислить здесь их все просто невозможно.
На внешнем уровне Scala позаимствовал существенную часть синтаксиса
у Java и C#, которые, в свою очередь, взяли большинство своих синтаксиче­
ских соглашений у C и C++. Выражения, инструкции и блоки — в основном
из Java, как, собственно, и синтаксис классов, создание пакетов и импорт1.
1

Главное отличие от Java касается синтаксиса для объявления типов: вместо «Тип
переменная», как в Java, задействуется форма «переменная: Тип». Используемый
в Scala постфиксный синтаксис типа похож на синтаксис, применяемый в Pascal,
Modula-2 или Eiffel. Основная причина такого отклонения имеет отношение к ло­
гическому выводу типов, зачастую позволяющему опускать тип переменной или
тип возвращаемого методом значения. Легче использовать синтаксис «переменная:
Тип», поскольку двоеточие и тип можно просто не указывать. Но в стиле языка C,
применяющем форму «Тип переменная», просто так не указывать тип нельзя,
поскольку при этом исчезнет сам признак начала определения. Неуказанный тип
в качестве заполнителя требует какое-нибудь ключевое слово (C# 3.0, в котором
имеется логический вывод типов, для этой цели задействует ключевое слово var).
Такое альтернативное ключевое слово представляется несколько более надуман­
ным и менее привычным, чем подход, который используется в Scala.

54   Глава 1



Масштабируемый язык

Кроме синтаксиса, Scala позаимствовал и другие элементы Java, такие как
его основные типы, библиотеки классов и модель выполнения.
Scala многим обязан и другим языкам. Его однородная модель объектов
впервые появилась в Smalltalk и впоследствии была принята языком Ruby.
Его идея универсальной вложенности (почти каждую конструкцию в Scala
можно вложить в любую другую) реализована также в Algol, Simula, а в по­
следнее время в Beta и gbeta. Его принцип единообразного доступа к вызову
методов и выбору полей пришел из Eiffel. Его подход к функциональному
программированию очень близок по духу к применяемому в семействе
языков ML, видными представителями которого являются SML, OCaml
и F#. Многие функции высшего порядка в стандартной библиотеке Scala
присутствуют также в ML или Haskell. Толчком для появления в Scala не­
явных параметров стали классы типов языка Haskell — в более классическом
объектно-ориентированном окружении они дают аналогичные результаты.
Используемая в Scala основная библиотека многопоточного вычисления на
основе акторов — Akka — создавалась под сильным влиянием особенностей
языка Erlang.
Scala не первый язык, делающий упор на масштабируемость и расширяе­
мость. Такое понятие, как расширяемые языки, которые могут охватывать
различные области применения, впервые встречается в статье Питера Лэнди­
на (Peter Landin) 1966 года (язык, описанный в этой статье, — Iswim — стоит
рядом с Lisp как один из первых функциональных языков) [Lan66]. Конкрет­
ная идея рассматривать инфиксный оператор как функцию восходит к Iswim
и Smalltalk. Другая важная идея — разрешить функциональный литерал
(или блок) в качестве параметра, который позволяет библиотекам опреде­
лять управляющие структуры. Опять же это восходит к Iswim и Smalltalk.
И Smalltalk, и Lisp обладают гибким синтаксисом, который широко приме­
нялся для создания внутренних специфичных для конкретной предметной
области языков. C++ — еще один масштабируемый язык, который можно
адаптировать и расширить с помощью перегрузки операторов и его систе­
мы шаблонов; по сравнению со Scala он построен на более низкоуровневом,
более системно — ориентированном ядре.
Кроме того, Scala не первый язык, объединяющий в себе функциональное
и объектно-ориентированное программирование, хотя, вероятно, в этом
направлении продвинулся гораздо дальше прочих. К числу других языков,
объединивших некоторые элементы функционального программирования
с объектно-ориентированным, относятся Ruby, Smalltalk и Python. Расши­
рения Java-подобного ядра некоторыми функциональными идеями были
предприняты на Java-платформе в Pizza, Nice, Multi-Java и самом Java 8.

Резюме   55

Существуют также изначально функциональные языки, которые приобрели
систему объектов. В качестве примера можно привести OCaml, F# и PLTScheme.
В Scala применяются также некоторые нововведения в области языков
программирования. Например, его абстрактные типы — более объектноориентированная альтернатива обобщенным типам, его трейты позволяют
выполнять гибкую сборку компонентов, а экстракторы обеспечивают неза­
висимый от представления способ сопоставления с образцом. Эти нововведе­
ния были озвучены в статьях на конференциях по языкам программирования
в последние годы1.

Резюме
Ознакомившись с текущей главой, вы получили некоторое представление
о том, что такое Scala и как он может помочь программисту в работе. Разу­
меется, этот язык не решит все ваши проблемы и не увеличит волшебным
образом вашу личную продуктивность. Следует заранее предупредить, что
Scala нужно применять искусно, а для этого потребуется получить некото­
рые знания и практические навыки. Если вы перешли к Scala от языка Java,
то одними из наиболее сложных аспектов его изучения для вас могут стать
система типов Scala, которая существенно богаче, чем у Java, и его поддерж­
ка функционального стиля программирования. Цель данной книги — по­
служить руководством при поэтапном, от простого к сложному, изучении
особенностей Scala. Полагаем, что вы приобретете весьма полезный интел­
лектуальный опыт, расширяющий ваш кругозор и изменяющий взгляд на
проектирование программных средств. Надеемся, что вдобавок вы получите
от программирования на Scala истинное удовольствие и познаете творческое
вдохновение.
В следующей главе вы приступите к написанию кода Scala.

1

Для получения дополнительной информации см. [Ode03], [Ode05] и [Emi07] в би­
блиографии.

2

Первые шаги в Scala

Пришло время написать какой-нибудь код на Scala. Прежде чем углубиться
в руководство по этому языку, мы приведем две обзорные главы по нему
и, что наиболее важно, заставим вас приступить к написанию кода. Реко­
мендуем по мере освоения материала на практике проверить работу всех
примеров кода, представленных в этой и последующей главах. Лучше всего
приступить к изучению Scala, программируя на данном языке.
Запуск представленных далее примеров возможен с помощью стандартной
установки Scala. Чтобы ее осуществить, перейдите по адресу www.scala-lang.
org/downloads и следуйте инструкциям для вашей платформы. На этой страни­
це описано несколько способов установки Scala. Будем считать, что вы уже
установили двоичные файлы Scala и добавили их в переменную окружения
path1, что необходимо для выполнения шагов из этой главы.
Если вы опытный программист, но новичок в Scala, то внимательно про­
читайте следующие две главы: в них приводится достаточный объем ин­
формации, позволяющий приступить к написанию полезных программ
на этом языке. Если же опыт программирования у вас невелик, то часть
материалов может показаться чем-то загадочным. Однако не стоит пере­
живать. Чтобы ускорить процесс изучения, нам пришлось обойтись без
некоторых подробностей. Более обстоятельные пояснения мы представим
в последующих главах. Кроме того, в следующих двух главах дадим ряд
сносок с указанием разделов книги, в которых можно найти более по­
дробные объяснения.

1

Мы протестировали примеры из этой книги со Scala версии 3.0.0.

Шаг 1. Осваиваем Scala REPL   57

Шаг 1. Осваиваем Scala REPL
Самый простой способ начать работу со Scala — использовать Scala REPL1,
интерактивную оболочку для написания выражений и программ Scala. REPL,
который называется scala, оценивает введенные вами выражения и выво­
дит полученное значение. Чтобы его использовать, нужно набрать scala
в командной строке2:
$ scala
Starting Scala REPL...
scala>

После того как вы наберете выражение, например 1 + 2, и нажмете клавишу
Enter:
scala> 1 + 2

REPL выведет на экран:
val res0: Int = 3

Эта строка включает:
zzключевое слово val, объявляющее переменную;
zzавтоматически

сгенерированное или определенное пользователем имя
для ссылки на вычисленное значение (res0, означающее результат 0);

zzдвоеточие (:), за которым следует тип выражения (Int);
zzзнак равенства (=);
zzзначение, полученное в результате вычисления выражения (3).

Тип Int означает класс Int в пакете scala . Пакеты в Scala аналогичны
пакетам в Java — они разбивают глобальное пространство имен на части
и предоставляют механизм для сокрытия данных3. Значения класса Int
соответствуют int-значениям в Java. Если говорить в общем, то все прими­
тивные типы Java имеют соответствующие классы в пакете scala. Например,
sca­la.Boolean соответствует Java-типу boolean. А scala.Float ­соответствует
1
2

3

REPL означает read, evaluate, print, loop («чтение, оценка, печать, цикл»).
Если вы используете Windows, вам нужно будет ввести команду scala в оболочке
командной строки.
Если вы не знакомы с пакетами Java, то их можно рассматривать как средство
предоставления классам полных имен. Int входит в пакет scala. Int — простое имя
класса, а scala.Int — полное. Подробнее о пакетах рассказывается в главе 12.

58   Глава 2



Первые шаги в Scala

Java-типу float. И при компиляции вашего кода Scala в байт-код Java ком­
пилятор Scala будет по возможности использовать примитивные типы Java,
чтобы обеспечить вам преимущество в производительности при работе
с примитивными типами.
Идентификатор resX может использоваться в последующих строках. Напри­
мер, поскольку ранее для res0 было установлено значение 3, то результат
выражения res0 * 3 будет равен 9:
scala> res0 * 3
val res1: Int = 9

Чтобы вывести на экран необходимое, но недостаточно информативное при­
ветствие Hello, world!, наберите следующую команду:
scala> println("Hello, world!")
Hello, world!

Функция println выводит на стандартное устройство вывода переданную ей
строку, подобно тому как это делает System.out.println в Java.

Шаг 2. Объявляем переменные
В Scala имеются две разновидности переменных: val-переменные и varпеременные. Первые аналогичны финальным переменным в Java. После
инициализации val -переменная уже никогда не может быть присвоена
повторно. В отличие от нее var-переменная аналогична нефинальной пере­
менной в Java и может быть присвоена повторно в течение своего жизненного
цикла. Определение val-переменной выглядит так:
scala> val msg = "Hello, world!"
val msg: String = Hello, world!

Эта инструкция вводит в употребление переменную msg в качестве имени
для строки "Hello, world!". Типом msg является java.lang.String, поскольку
строки в JVM Scala реализуются Java-классом String.
Если вы привыкли объявлять переменные в Java, то в этом примере кода
можете заметить одно существенное отличие: в val-определении нигде не
фигурируют ни java.lang.String, ни String. Пример демонстрирует логический вывод типов, то есть возможность Scala определять неуказанные
типы. В данном случае, поскольку вы инициализировали msg строковым
литералом, Scala придет к выводу, что типом msg должен быть String. ­Когда

Шаг 2. Объявляем переменные   59

REPL (или компилятор) Scala хочет выполнить вывод типов, зачастую
лучше всего будет позволить ему сделать это, не засоряя код ненужными
явными аннотациями типов. Но при желании можете указать тип явно,
и, вероятно, иногда это придется делать. Явная аннотация типа может не
только гарантировать, что компилятор Scala выведет желаемый тип, но
и послужить полезной документацией для тех, кто впоследствии станет
читать ваш код. В отличие от Java, где тип переменной указывается перед
ее именем, в Scala вы указываете тип переменной после ее имени, отделяя
его двоеточием, например:
scala> val msg2: java.lang.String = "Hello again, world!"
val msg2: String = Hello again, world!

Или же, поскольку типы java.lang вполне опознаваемы в программах на
Scala по их простым именам1, запись можно упростить:
scala> val msg3: String = "Hello yet again, world!"
msg3: String = Hello yet again, world!

Возвратимся к исходной переменной msg. Поскольку она определена, то ею
можно воспользоваться в соответствии с вашими ожиданиями, например:
scala> println(msg)
Hello, world!

Учитывая, что msg является val-, а не var-переменной, вы не сможете по­
вторно присвоить ей другое значение2. Посмотрите, к примеру, как REPL
выражает свое недовольство при попытке сделать следующее:
scala> msg = "Goodbye cruel world!"
1 |msg = "Goodbye cruel world!"
|ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ
|Reassignment to val msg

Если необходимо выполнить повторное присваивание, следует воспользо­
ваться var-переменной:
scala> var greeting = "Hello, world!"
var greeting: String = Hello, world!

Как только приветствие станет var-, а не val-переменной, ему можно будет
присвоить другое значение. Если, к примеру, чуть позже вы станете более
1
2

Простым именем java.lang.String является String.
Но в интерпретаторе новую val-переменную можно определить с именем, которое
до этого уже использовалось. Этот механизм рассматривается в разделе 7.7.

60   Глава 2



Первые шаги в Scala

раздражительными, то можете поменять приветствие на просьбу оставить
вас в покое:
scala> greeting = "Leave me alone, world!"
greeting: String = Leave me alone, world!

Чтобы ввести в REPL код, который не помещается в одну строку, просто про­
должайте набирать код после заполнения первой строки. Если набор кода
еще не завершен, то REPL отреагирует установкой на следующей строке
вертикальной черты:
scala> val multiLine =
|
"This is the next line."
multiLine: String = This is the next line.

Если вы понимаете, что набрали что-то не так, но REPL все еще ожидает ввода
дополнительных данных, вы можете использовать клавиши со стрелками для
перемещения вверх, вниз, влево или вправо, чтобы исправить ошибки. Если
вы хотите полностью отменить ввод, вы можете выйти, дважды нажав Enter:
scala> val oops =
|
|
You typed two blank lines. Starting a new command.
scala>

Далее по тексту мы чаще всего будем опускать подсказку scala, верикальные
линии и вывод REPL при успешном вводе, чтобы упростить чтение кода
(и облегчить копирование и вставку из электронной книги PDF в REPL).

Шаг 3. Определяем функции
После работы с переменными в Scala вам, вероятно, захотелось написать
какие-нибудь функции. Это делается так:
def max(x: Int, y: Int): Int =
if x > y then x
else y

Определение функции начинается с ключевого слова def. После имени функ­
ции, в данном случае max, стоит заключенный в круглые скобки перечень
параметров, разделенных запятыми. За каждым параметром функции должна
следовать аннотация типа, перед которой ставится двоеточие, поскольку ком­
пилятор Scala (и REPL, но с этого момента будет упоминаться только компи­

Шаг 3. Определяем функции   61

лятор) не выводит типы параметров функции. В данном примере функция
по имени max получает два параметра, x и y, и оба они относятся к типу Int.
После закрывающей круглой скобки перечня параметров функции max обна­
руживается аннотация типа : Int. Она определяет результирующий тип самой
функции max1. За типом результата функции следует знак равенства и тело
функции, которое отделено отступами. В этом случае тело содержит одно
выражение if, которое в качестве результата функции max выбирает либо x,
либо y, в зависимости от того, что больше. Как показано здесь, выражение if
в Scala может приводить к значению, аналогичному тернарному оператору
Java. Например, в Scala выражение if x > y then x else y вычисляется точно
так же, как выражение (x > y) ? x : y в Java. Знак равенства, предшествующий
телу функции, дает понять, что с точки зрения функционального мира функ­
ция определяет выражение, результатом вычисления которого становится
значение. Основная структура функции показана на рис. 2.1.

Рис. 2.1. Основная форма определения функции в Scala

Иногда компилятор Scala может потребовать от васуказать результиру­
ющий тип функции. Если, к примеру, функция является рекурсивной2, то
вы должны указать ее результирующий тип явно. Но в случае с функцией
max вы можете не указывать результирующий тип функции — компилятор
выведет его самостоятельно3. Кроме того, если функция состоит всего лишь
1

2
3

В Java тип возвращаемого из метода значения является возвращаемым типом.
В Scala то же самое понятие называется результирующим типом.
Функция называется рекурсивной, если вызывает саму себя.
Тем не менее зачастую есть смысл указывать результирующий тип явно, даже когда
компилятор этого не требует. Такая аннотация типа может упростить чтение кода,
поскольку читателю не придется изучать тело функции, чтобы определить, каким
будет вывод результирующего типа.

62   Глава 2



Первые шаги в Scala

из одного оператора, вы сможете целиком написать ее в одну строку. Таким
образом, у вас появляется альтернативный вариант реализации функции max:
def max(x: Int, y: Int) = if x > y then x else y

После того как вы определили функцию, вы можете вызвать ее по имени,
например:
val bigger = max(3, 5) // 5

А вот определение функции, которая не принимает никаких параметров и не
возвращает какого-либо интересного результата:
scala> def greet() = println("Hello, world!")
def greet(): Unit

Когда определяется функция приветствия greet(), REPL откликается сле­
дующим приветствием: def greet (): Unit. Разумеется, слово greet — это
имя функции. Пустота в скобках показывает, что функция не получает
параметров. А Unit — результирующий тип функции greet. Он показывает,
что функция не возвращает никакого интересного значения. Тип Unit в Scala
подобен типу void в Java. Фактически каждый метод, возвращающий void
в Java, отображается на метод, возвращающий Unit в Scala. Таким образом,
методы с результирующим типом Unit выполняются только для того, чтобы
проявились их побочные эффекты. В случае с greet() побочным эффектом
будет дружеское приветствие, выведенное на стандартное устройство вывода.
При выполнении следующего шага код Scala будет помещен в файл и запу­
щен в качестве скрипта. Если нужно выйти из REPL, то это можно сделать
с помощью команды :quit:
scala> :quit
$

Шаг 4. Пишем Scala-скрипты
Несмотря на то что язык Scala разработан, чтобы помочь программистам
создавать очень большие масштабируемые системы, он вполне может подой­
ти и для решения менее масштабных задач наподобие написания скриптов.
Скрипт — это просто исходный файл Scala, который содержит функцию
верхнего уровня, определяемую как @main . Поместите в файл по имени
hello.scala следующий код:
@main def m() =
println("Hello, world, from a script!")

Шаг 4. Пишем Scala-скрипты   63

а затем запустите файл на выполнение:
$ scala hello.scala

И вы получите еще одно приветствие:
Hello, world, from a script!

В этом примере функция, отмеченная @main, называется m (от слова main), но
это имя не имеет значения для выполнения скрипта. Чтобы скрипт сработал,
вам необходимо запустить Scala и указать имя файла, содержащего функцию
main, а не имя этой функции.
Вы можете получить доступ к аргументам командной строки, переданным
вашему скрипту, приняв их в качестве параметров вашей основной функ­
ции. Например, вы можете принять строковые аргументы, взяв параметр со
специальной аннотацией типа String*, что означает от нуля до многих по­
вторяющихся параметров типа String1. Внутри основной функции параметр
будет иметь тип Seq[String], то есть последовательность строк. В Scala по­
следовательности начинаются с нуля, и чтобы получить доступ к элементу,
необходимо указать его индекс в круглых скобках. Таким образом, первым
элементом в последовательности Scala с именем steps будет steps(0). Чтобы
попробовать это, введите в новый файл с именем helloarg.scala следующее:
@main def m(args: String*) =
// Поприветствуйте содержимое первого аргумента
println("Hello, " + args(0) + "!")

а затем запустите его на выполнение:
$ scala helloarg.scala planet

В данной команде planet передается в качестве аргумента командной строки,
доступного в скрипте при использовании выражения args(0). Поэтому вы
должны увидеть на экране следующий текст:
Hello, planet!

Обратите внимание на наличие комментария в скрипте. Компилятор Scala
проигнорирует символы между парой символов // и концом строки, а также
все символы между сочетаниями символов /* и */. Вдобавок в этом примере
показана конкатенация String-значений, выполненная с помощью операто­
ра +. Весь код работает вполне предсказуемо. Выражение "Hello, " + "world!"
будет вычислено в строку "Hello, world!".
1

Повторяющиеся параметры описаны в разделе 8.8.

64   Глава 2



Первые шаги в Scala

Шаг 5. Организуем цикл с while и принимаем
решение с if
Чтобы попробовать в работе конструкцию while, наберите следующий код
и сохраните его в файле printargs.scala:
@main def m(args: String*) =
var i = 0
while i < args.length do
println(args(i))
i += 1

ПРИМЕЧАНИЕ
Хотя примеры в данном разделе помогают объяснить суть циклов while, они
не демонстрируют наилучший стиль программирования на Scala. В следу­
ющем разделе будут показаны более рациональные подходы, позволяющие
избежать повторения последовательностей с помощью индексов.

Этот скрипт начинается с определения переменой, var i = 0. Вывод типов
относит переменную i к типу Int, поскольку это тип ее начального значе­
ния 0. Конструкция while на следующей строке заставляет блок (две строки
кода снизу) повторно выполняться, пока булево выражение i < args.length
будет вычисляться в false . Метод args.length вычисляет длину после­
довательности args . Блок содержит две инструкции, каждая из которых
набрана с отступом в два пробела, что является рекомендуемым стилем от­
ступов для кода на Scala. Первая инструкция, println(args(i)), выводит на
экран i-й аргумент командной строки. Вторая, i += 1, увеличивает значение
переменной i на единицу. Обратите внимание: Java-код ++i и i++ в Scala не
работает. Чтобы в Scala увеличить значение переменной на единицу, нужно
использовать одно из двух выражений: либо i = i + 1, либо i += 1. Запустите
этот скрипт с помощью команды, показанной ниже:
$ scala printargs.scala Scala is fun

И вы увидите:
Scala
is
fun

Далее наберите в новом файле по имени echoargs.scala следующий код:
@main def m(args: String*) =
var i = 0

Шаг 5. Организуем цикл с while и принимаем решение с if   65

while i < args.length do
if i != 0 then
print(" ")
print(args(i))
i += 1
println()

В целях вывода всех аргументов в одной и той же строке в этой версии вме­
сто вызова println используется вызов print. Чтобы эту строку можно было
прочитать, перед каждым аргументом, за исключением первого, благодаря
использованию конструкции if i != 0 then вставляется пробел. При первом
проходе цикла while выражение i != 0 станет вычисляться в false, поэтому
перед начальным элементом пробел выводиться не будет. В самом конце
добавлена еще одна инструкция println, чтобы после вывода аргументов
произошел переход на новую строку. Тогда у вас получится очень красивая
картинка. Если запустить этот скрипт с помощью команды:
$ scala echoargs.scala Scala is even more fun

то вы увидите на экране такой текст:
Scala is even more fun

Обратите внимание, что в Scala, в отличие от Java, вам не нужно помещать
логическое выражение while или if в круглые скобки. Еще одно отличие от
Java состоит в том, что вы можете опустить фигурные скобки в блоке, даже
если он содержит более одного оператора, при условии, что вы сделаете со­
ответствующий отступ для каждой строки. И хотя вы не видели ни одной
точки с запятой, Scala использует их для разделения операторов, как и Java,
за исключением того, что в Scala эти знаки очень часто являются необяза­
тельными, что дает некоторое облегчение вашему правому мизинцу. Если бы
вы были более многословны, вы могли бы написать скрипт echoargs.scala
в стиле Java следующим образом:
@main def m(args: String*) = {
var i = 0;
while (i < args.length) {
if (i != 0) {
print(" ");
}
print(args(i));
i += 1;
}
println();
}

66   Глава 2



Первые шаги в Scala

Начиная со Scala 3, вместо фигурных скобок рекомендуется использовать
стиль на основе отступов, называемый «тихим синтаксисом». В Scala 3
также были добавлены маркеры окончания кода, помогающие понять, где
заканчиваются более крупные области с отступом. Маркеры окончания кода
состоят из ключевого слова end и следующего за ним токена спецификатора,
который является либо идентификатором, либо ключевым словом. Пример
показан в листинге 10.9.

Шаг 6. Перебираем элементы с foreach
и for-do
Возможно, при написании циклов while на предыдущем шаге вы даже не
осознавали того, что программирование велось в императивном стиле. Обыч­
но он применяется с такими языками, как Java, C++ и Python. При работе
в этом стиле императивные команды в случае последовательного перебора
элементов в цикле выдаются поочередно и зачастую изменяемое состояние
совместно используется различными функциями. Scala позволяет програм­
мировать в императивном стиле, но, узнав этот язык получше, вы, скорее
всего, перейдете преимущественно на функциональный стиль. По сути, одна
из основных целей этой книги — помочь освоить работу в функциональном
стиле, чтобы она стала такой же комфортной, как и работа в императивном.
Одна из основных характеристик функционального языка — то, что его
функции относятся к конструкциям первого класса, и это абсолютно спра­
ведливо для языка Scala. Например, еще один, гораздо более лаконичный
вариант вывода каждого аргумента командной строки выглядит так:
@main def m(args: String*) =
args.foreach(arg => println(arg))

В этом коде в отношении массива args вызывается метод foreach, в который
передается функция. В данном случае передается функциональный литерал
с одним параметром arg. Тело функции — вызов println(arg). Если набрать
показанный ранее код в новом файле по имени pa.scala и запустить этот
файл на выполнение с помощью команды:
$ scala pa.scala Concise is nice

то на экране появятся строки:
Concise
is
nice

Шаг 6. Перебираем элементы с foreach и for-do   67

В предыдущем примере компилятор Scala вывел тип arg, причислив эту
переменную к String, поскольку String — тип элемента последовательно­
сти, в отношении которого вызван метод foreach. Если вы предпочитаете
конкретизировать, то можете упомянуть название типа. Но, пойдя по этому
пути, придется часть кода, в которой указывается переменная аргумента,
заключать в круглые скобки (это и есть обычный синтаксис):
@main def m(args: String*) =
args.foreach((arg: String) => println(arg))

При запуске этот скрипт ведет себя точно так же, как и предыдущий.
Если же вы склонны не к конкретизации, а к более лаконичному изложению
кода, то можете воспользоваться специальными сокращениями, принятыми
в Scala. Если функциональный литерал функции состоит из одной инструк­
ции, принимающей один аргумент, то обозначать данный аргумент явным
образом по имени не нужно1. Поэтому работать будет и следующий код:
@main def m(args: String*) =
args.foreach(println)

Резюмируем усвоенное: синтаксис для функционального литерала пред­
ставляет собой список поименованных параметров, заключенный в круглые
скобки, а также правую стрелку, за которой следует тело функции. Этот
синтаксис показан на рис. 2.2.

Рис. 2.2. Синтаксис функционального литерала в Scala

Теперь вы можете поинтересоваться: что же случилось с теми проверенными
циклами for, которые вы привыкли использовать в таких императивных язы­
ках, как Java или Python? Придерживаться функционального направления
в Scala возможно с помощью только одного функционального родственни­
ка императивной конструкции for, который называется выражением for.
1

Это сокращение, которое называется частично применяемой функцией, описано
в разделе 8.6.

68   Глава 2



Первые шаги в Scala

­ оскольку вы не сможете понять всю его эффективность и выразитель­
П
ность, пока не доберетесь до раздела 7.3 (или не заглянете в него), здесь
о нем будет дано лишь общее представление. Наберите в новом файле по
имени forargs.scala следующий код:
@main def m(args: String*) =
for arg "Go to island.")
treasureMap += (2 –> "Find big X on ground.")
treasureMap += (3 –> "Dig.")
val step2 = treasureMap(2) // " Find big X on ground."

Например, в листинге 3.7 показана работа с изменяемым отображением:
в первой строке оно импортируется, затем определяется val-переменная
treasureMap , которая инициализируется пустым изменяемым отображе­
нием, имеющим целочисленные ключи и строковые значения. Оно пустое,
поскольку вызывается фабричный метод с именем empty и указывается Int
в качестве типа ключа и String в качестве типа значения1. В следующих трех
строках к отображению добавляются пары «ключ — значение», для чего ис­
пользуются методы –> и +=. Как уже было показано, компилятор Scala преоб­
разует выражения бинарных операций вида 1 –> "Go to island." в код (1).–>
("Go to island."). Следовательно, когда указывается 1 –> "Go to island.",
фактически в отношении объекта 1 вызывается метод по имени –>, которому
передается строка со значением "Go to island.". Метод –>, который можно
вызвать в отношении любого объекта в программе Scala, возвращает двух­
элементный кортеж, содержащий ключ и значение2. Затем этот кортеж пере­
дается методу += объекта отображения, на который ссылается treasureMap.
И наконец, в последней строке ищется значение, соответствующее клю­
чу 2 в treasureMap. После выполнения этого кода переменная step2 будет
­ссылаться на "Find big X on ground".
Если отдать предпочтение неизменяемому отображению, то ничего импор­
тировать не нужно, поскольку это отображение используется по умолчанию.
Пример показан в листинге 3.8.
1

2

Явная параметризация типа "[Int, String]" требуется в листинге 3.7 из-за того,
что без какого-либо значения, переданного фабричному методу, компилятор не
в состоянии выполнить логический вывод типов параметров отображения. В от­
личие от этого компилятор может выполнить вывод типов параметров из значений,
переданных фабричному методу map, показанному в листинге 3.8, поэтому явного
указания типов параметров там не требуется.
Механизм Scala, позволяющий вызывать такие методы, как –> для объектов,
которые не объявляют их напрямую, называется методом расширения. Он будет
рассмотрен в главе 22.

84   Глава 3



Дальнейшие шаги в Scala

Листинг 3.8. Создание, инициализация и использование
неизменяемого отображения
val romanNumeral = Map(
1 –> "I", 2 –> "II", 3 –> "III", 4 –> "IV", 5 –> "V"
)
val four = romanNumeral(4) // "IV"

Учитывая отсутствие импортирования, при указании Map в первой строке
данного листинга вы получаете используемый по умолчанию экземпляр
класса scala.collection.immutable.Map. Фабричному методу отображения
передаются пять кортежей «ключ — значение», а он возвращает неизменяе­
мое Map-отображение, содержащее эти переданные пары. Если запустить код,
показанный в листинге 3.8, то переменная 4 будет ссылаться на IV.

Шаг 11. Учимся распознавать
функциональный стиль
Как упоминалось в главе 1, Scala позволяет программировать в императив­
ном стиле, но побуждает вас переходить преимущественно к функциональ­
ному. Если к Scala вы пришли, имея опыт работы в императивном стиле,
к примеру, вам приходилось программировать на Java, то одной из основных
возможных сложностей станет программирование в функциональном стиле.
Мы понимаем, что поначалу этот стиль может быть неизвестен, и в данной
книге стараемся перевести вас из одного состояния в другое. От вас также
потребуются некоторые усилия, которые мы настоятельно рекомендуем при­
ложить. Мы уверены, что при наличии опыта работы в императивном стиле
изучение программирования в функциональном позволит вам не только
стать более квалифицированным программистом на Scala, но и расширит
ваш кругозор, сделав вас более ценным программистом в общем смысле.
Сначала нужно усвоить разницу между двумя стилями, отражающуюся
в коде. Один верный признак заключается в том, что если код содержит
var -переменные, то он, вероятнее всего, написан в императивном стиле.
Если он вообще не содержит var -переменных, то есть включает только
val-переменные, то, вероятнее всего, он написан в функциональном стиле.
Следовательно, один из способов приблизиться к последнему — попытаться
обойтись в программах без var-переменных.
Обладая багажом императивности, то есть опытом работы с такими языка­
ми, как Java, C++ или C#, var-переменные можно рассматривать в качестве
обычных, а val-переменные — в качестве переменных особого вида. В то же

Шаг 11. Учимся распознавать функциональный стиль   85

время, если у вас имеется опыт работы в функциональном стиле на таких
языках, как Haskell, OCaml или Erlang, val-переменные можно представ­
лять как обычные, а var-переменные — как некое кощунственное обращение
с кодом. Но с точки зрения Scala val- и var-переменные — всего лишь два
разных инструмента в вашем арсенале средств и оба одинаково полезны и не
отвергаемы. Scala побуждает вас к использованию val-переменных, но, по
сути, дает возможность применять тот инструмент, который лучше подходит
для решаемой задачи. И тем не менее, даже будучи согласными с подобной
философией, вы поначалу можете испытывать трудности, связанные с из­
бавлением от var-переменных в коде.
Рассмотрим позаимствованный из главы 2 пример цикла while, в котором
используется var-переменная, означающая, что он выполнен в императивном
стиле:
def printArgs(args: List[String]): Unit =
var i = 0
while i < args.length do
println(args(i))
i += 1

Вы можете преобразовать этот код — придать ему более функциональный
стиль, отказавшись от использования var-переменной, например, так:
def printArgs(args: List[String]): Unit =
for arg adj + " Fish")
// List(One Fish, Two Fish, Red Fish, Blue Fish)

Другой способ выполнить преобразование — использовать выражение for,
в котором вы вводите тело функции с ключевым словом yield вместо do:
val nouns =
for adj noun.length)
// List(8, 8, 8, 9)

Как и раньше, вы также можете использовать выражение for-yield для до­
стижения того же преобразования:
val lengths =
for noun q.toLowerCase + "?")
// Vector(who?, what?, when?, where?, why?)
val usingForYield =
for q
=>
=>
=>

q.startsWith("W"))
q.length == 4)
q.length == 5)
q.startsWith("H"))

//
//
//
//

Some(Who)
Some(What)
Some(Where)
None

Хотя Option не является коллекцией, он предлагает map-метод2. Если Opti­
on является Some , который называется «определенным» параметром, map
1

2

В Java 8 к стандартной библиотеке был добавлен тип Optional, но многие суще­
ствующие библиотеки Java по-прежнему используют null для обозначения от­
сутствующего необязательного значения.
Однако Option можно представить как набор, который содержит либо ноль (случай
None) элементов, либо один (случай Some).

90   Глава 3



Дальнейшие шаги в Scala

­ озвращает новый Option, содержащий результат передачи исходного эле­
в
мента Some в функцию, переданную в map . Вот пример преобразования
startsW в Some, содержащего строку WHO:
startsW.map(word => word.toUpperCase) // Some(WHO)

Как и в случае с List и Vector, вы можете добиться того же преобразования
в Option с помощью for-yield:
for word word.toUpperCase) // None

А вот такое же преобразование с использованием for-yield:
for word 8).toByte)
acc.add(c.toByte)
val cs = acc.checksum()
cache += (s –> cs)
cs

Объект-одиночка ChecksumAccumulator располагает одним методом по имени
calculate, который получает строку String и вычисляет контрольную сумму
символов этой строки. Вдобавок он имеет одно приватное поле cache, пред­
ставленное изменяемым отображением, в котором кэшируются ранее вычис­
ленные контрольные суммы1. В первой строке метода, "if cache.contains(s)
1

Здесь cache используется, чтобы показать объект-одиночку с полем. Кэширование
с помощью поля cache помогает оптимизировать производительность, сокращая
за счет расхода памяти время вычисления и разменивая расход памяти на время
вычисления. Как правило, использовать кэш-память таким образом целесообразно
только в том случае, если с ее помощью можно решить проблемы производительно­
сти и воспользоваться отображением со слабыми ссылками, например WeakHashMap
в scala.collection.mutable, чтобы записи в кэш-памяти могли попадать в сборщик
мусора при наличии дефицита памяти.

98   Глава 4



Классы и объекты

then", определяется, не содержится ли в отображении cache переданная строка

в качестве ключа. Если да, то просто возвращается отображенное на этот ключ
значение cache(s). В противном случае выполняется условие else, которое
вычисляет контрольную сумму. В первой строке условия else определяется
val-переменная по имени acc, которая инициализируется новым экземпля­
ром ChecksumAccumulator1. В следующей строке находится выражение for.
Оно выполняет последовательный перебор каждого символа в переданной
строке, преобразует символ в значение типа Byte, вызывая в отношении это­
го символа метод toByte, и передает результат в метод add того экземпляра
ChecksumAccumulator, на который ссылается acc2. Когда завершится вычисле­
ние выражения for, в следующей строке метода в отношении acc будет вызван
метод checksum, который берет контрольную сумму для переданного значения
типа String и сохраняет ее в val-переменной по имени cs.
В следующей строке, cache += (s –> cs), переданный строковый ключ отобра­
жается на целочисленное значение контрольной суммы, и эта пара «ключ —
значение» добавляется в отображение cache. В последнем выражении метода,
cs, обеспечивается использование контрольной суммы в качестве результата
выполнения метода.
Если у вас есть опыт программирования на Java, то объекты-одиночки можно
представить в качестве хранилища для любых статических методов, которые
вполне могли быть написаны на Java. Методы в объектах-одиночках можно
вызывать с помощью такого синтаксиса: имя объекта, точка, имя метода.
Например, метод calculate объекта-одиночки ChecksumAccumulator можно
вызвать следующим образом:
ChecksumAccumulator.calculate("Every value is an object.")

Но объект-одиночка не только хранилище статических методов. Он объект
первого класса. Поэтому имя объекта-одиночки можно рассматривать в ка­
честве «этикетки», прикрепленной к объекту.

1

2

Поскольку ключевое слово new используется только для создания экземпляров
классов, новый объект, созданный здесь в качестве экземпляра класса ChecksumAc­
cumulator, не является одноименным объектом-одиночкой.
Оператор >>, выполняющий побитовый сдвиг вправо, описан в разделе 5.7.

4.4. Case-классы   99

Определение объекта-одиночки не является определением типа на том уровне
абстракции, который используется в Scala. Имея лишь определение объекта
ChecksumAccumulator, невозможно создать одноименную переменную типа.
Точнее, тип с именем ChecksumAccumulator определяется классом-компаньоном
объекта-одиночки. Тем не менее объекты-одиночки расширяют суперкласс
и могут подмешивать трейты. Учитывая то, что каждый объект-одиночка — эк­
земпляр своего суперкласса и подмешанных в него трейтов, его методы можно
вызывать через эти типы, ссылаясь на него из переменных этих типов и пере­
давая ему методы, ожидающие использования этих типов. Примеры объектоводиночек, являющихся наследниками классов и трейтов, показаны в главе 12.
Одно из отличий классов от объектов-одиночек состоит в том, что объектыодиночки не могут принимать параметры, а классы — могут. Создать экзем­
пляр объекта-одиночки с помощью ключевого слова new нельзя, поэтому
передать ему параметры не представляется возможным. Каждый объект-оди­
ночка реализуется как экземпляр синтетического класса, ссылка на который
находится в статической переменной, поэтому у них и у статических классов
Java одинаковая семантика инициализации1. В частности, объект-одиночка
инициализируется при первом обращении к нему какого-либо кода.
Объект-одиночка, который не имеет общего имени с классом-компаньоном,
называется самостоятельным. Такие объекты можно применять для решения
многих задач, включая сбор в одно целое родственных вспомогательных ме­
тодов или определение точки входа в приложение Scala. Именно этот случай
мы и рассмотрим в следующем разделе.

4.4. Case-классы
Часто при написании класса вам потребуется реализация таких методов,
как equals, hashCode, toString — методы доступа или фабричные методы.
Их написание может занять много времени и привести к ошибкам. Scala
предлагает такой инструмент, как case-классы (классы-образцы), которые
могут генерировать реализации нескольких методов на основе значений,
переданных его основному конструктору. Вы создаете класс case, помещая
модификатор case перед class, например:
case class Person(name: String, age: Int)

1

В качестве имени синтетического класса используется имя объекта со знаком дол­
лара. Следовательно, синтетический класс, применяемый для объекта-одиночки
Check­sumAccumulator, называется ChecksumAccumulator$.

100   Глава 4



Классы и объекты

С добавлением модификатора case компилятор сгенерирует для вас несколь­
ко полезных методов. Во-первых, компилятор создаст объект-компаньон
и поместит в него фабричный метод с именем apply . Таким образом, вы
можете создать новый объект Person следующим образом:
val p = Person("Sally", 39)

Компилятор перепишет эту строку кода в вызов сгенерированного фабрич­
ного метода: Person.apply("Sally", 39).
Во-вторых, компилятор будет хранить все параметры класса в полях и ге­
нерировать методы доступа с тем же именем, что и у заданного параметра1.
Например, вы можете получить доступ к заданным в Person значениям име­
ни и возраста следующим образом:
p.name // Sally
p.age // 39

В-третьих, компилятор предоставит вам реализацию toString:
p.toString // Person(Sally,39)

В-четвертых, компилятор сгенерирует реализацию hashCode и equals для
вашего класса. Эти методы будут основывать свой результат на параметрах,
переданных конструктору. Например, объект Person будет учитывать и имя,
и возраст при сравнении:
p == Person("Sally", 21)
// false
p.hashCode == Person("Sally", 21).hashCode // false
p == Person("James", 39)
// false
p.hashCode == Person("James", 39).hashCode // false
p == Person("Sally", 39)
// true
p.hashCode == Person("Sally", 39).hashCode // true

Компилятор не будет генерировать метод, который вы реализуете самостоя­
тельно. Он будет использовать вашу реализацию. Вы также можете добавить
другие поля и методы к классу и его компаньону. Вот пример, в котором вы
определяете метод apply в сопутствующем объекте Person (компилятор не
будет его генерировать) и добавляете метод appendToName в класс:
case class Person(name: String, age: Int):
def appendToName(suffix: String): Person =
Person(s"$name$suffix", age)

1

Они называются параметрическими полями, которые будут описаны в разделе 10.6.

4.5. Приложение на языке Scala   101

object Person:
// Убедитесь, что непустое имя написано с заглавной буквы
def apply(name: String, age: Int): Person =
val capitalizedName =
if !name.isEmpty then
val firstChar = name.charAt(0).toUpper
val restOfName = name.substring(1)
s"$firstChar$restOfName"
else throw new IllegalArgumentException("Empty name")
new Person(capitalizedName, age)

Этот apply-метод гарантирует, что первый символ имени будет начинаться
с заглавной буквы:
val q = Person("sally", 39) // Person(Sally,39)

Вы также можете вызвать метод appendToName , который вы определили
в классе:
q.appendToName(" Smith") // Person(Sally Smith,39)

Наконец, компилятор добавляет метод copy в ваш класс и метод unapply
к компаньону. Они будут описаны в главе 13.
Все эти условности облегчают работу, но с небольшой оговоркой: вам всего
лишь понадобится написать модификатор case, а ваши классы и объекты
при этом станут немного больше. Они вырастают, потому что генерируются
дополнительные методы и для каждого параметра конструктора добавляется
неявное поле.

4.5. Приложение на языке Scala
Чтобы запустить программу на Scala, нужно предоставить имя автономного
объекта-одиночки с методом main, который получает один параметр с типом
Array[String] и имеет результирующий тип Unit. Точкой входа в приложе­
ние может стать любой самостоятельный объект с методом main, имеющим
надлежащую сигнатуру1. Пример показан в листинге 4.3.
Листинг 4.3. Приложение Summer
// Код находится в файле Summer.scala
import ChecksumAccumulator.calculate
1

Вы можете обозначить методы другими именами в качестве основных функций
с помощью @main. Этот метод будет описан в разделе 23.3.

102   Глава 4



Классы и объекты

object Summer:
def main(args: Array[String]): Unit =
for arg val a = 'A'
val a: Char = A

Помимо того что символ представляется в одинарных кавычках в явном
виде, его можно указывать с помощью кода из таблицы символов Unicode.
Для этого нужно записать \u, после чего указать четыре шестнадцатеричные
цифры кода:
scala>
val d:
scala>
val f:

val d = '\u0041'
Char = A
val f = '\u0044'
Char = D

Такие символы в кодировке Unicode могут появляться в любом месте про­
граммы на языке Scala. Например, вы можете набрать следующий иденти­
фикатор:
scala> val B\u0041\u0044 = 1
val BAD: Int = 1

Он рассматривается точно так же, как идентификатор BAD , являющийся
результатом раскрытия символов в кодировке Unicode в показанном ранее
коде. По сути, в именовании идентификаторов подобным образом нет ничего
хорошего, поскольку их трудно прочесть. Иногда с помощью этого синта­
ксиса исходные файлы Scala, которые содержат отсутствующие в таблице
ASCII символы из таблицы Unicode, можно представить в кодировке ASCII.
И наконец, нужно упомянуть о нескольких символьных литералах, пред­
ставленных специальными управляющими последовательностями (escape
sequences), показанными в табл. 5.2, например:
scala> val backslash = '\\'
val backslash: Char = \

5.2. Литералы   109

Таблица 5.2. Управляющие последовательности специальных символьных литералов
Литерал

Предназначение

\n

Перевод строки (\u000A)

\b

Возврат на одну позицию (\u0008)

\t

Табуляция (\u0009)

\f

Перевод страницы (\u000C)

\r

Возврат каретки (\u000D)

\"

Двойные кавычки (\u0022)

\'

Одинарная кавычка (\u0027)

\\

Обратный слеш (\u005C)

Строковые литералы
Строковый литерал состоит из символов, заключенных в двойные кавычки:
scala> val hello = "hello"
val hello: String = hello

Синтаксис символов внутри кавычек такой же, как и в символьных литера­
лах, например:
scala> val escapes = "\\\"\'"
val escapes: String = \"'

Данный синтаксис неудобен для строк, в которых содержится множество
управляющих последовательностей, или для строк, не умещающихся в одну
строку текста, поэтому для неформатированных строк в Scala включен
специальный синтаксис. Неформатированная строка начинается и закан­
чивается тремя идущими подряд двойными кавычками ("""). Внутри нее
могут содержаться любые символы, включая символы новой строки, кавычки
и специальные символы, за исключением, разумеется, трех кавычек подряд.
Например, следующая программа выводит сообщение, используя неформа­
тированную строку:
println("""Welcome to Ultamix 3000.
Type "HELP" for help.""")

Но при запуске этого кода получается не совсем то, что хотелось:
Welcome to Ultamix 3000.
Type "HELP" for help.

110   Глава 5



Основные типы и операции

Проблема во включении в строку пробелов перед второй строкой текста!
Чтобы справиться с этой весьма часто возникающей ситуацией, вы можете
вызывать в отношении строк метод stripMargin. Чтобы им воспользоваться,
поставьте символ вертикальной черты (|) перед каждой строкой текста, а за­
тем в отношении всей строки вызовите метод stripMargin:
println("""|Welcome to Ultamix 3000.
|Type "HELP" for help.""".stripMargin)

Вот теперь код ведет себя подобающим образом:
Welcome to Ultamix 3000.
Type "HELP" for help.

Булевы литералы
У типа Boolean имеется два литерала, true и false:
val bool = true // true: Boolean
val fool = false // false: Boolean

Вот, собственно, и все. Теперь вы буквально (или литерально) стали боль­
шим специалистом по Scala.

5.3. Интерполяция строк
В Scala включен довольно гибкий механизм для интерполяции строк,
позволяющий вставлять выражения в строковые литералы. В самом рас­
пространенном случае использования этот механизм предоставляет лако­
ничную и удобочитаемую альтернативу конкатенации строк. Рассмотрим
пример:
val name = "reader"
println(s"Hello, $name!")

Выражение s"Hello, $name!" — обрабатываемый строковый литерал. По­
скольку за буквой s стоят открывающие кавычки, то Scala для обработки
литерала воспользуется интерполятором строк s. Он станет вычислять
каждое встроенное выражение, вызывая в отношении каждого результата
метод toString и заменяя встроенные выражения в литерале этими резуль­
татами. Таким образом, из s"Hello, $name!" получится "Hello, reader!",

5.3. Интерполяция строк   111

то есть точно такой же результат, как при использовании кода "Hello, " +
name + "!".
После знака доллара ($) в обрабатываемом строковом литерале можно ука­
зать любое выражение. Для выражений с одной переменной зачастую можно
просто поместить после знака доллара имя этой переменной. Все символы,
вплоть до первого символа, не относящегося к идентификатору, Scala будет
интерпретировать как выражение. Если в него включены символы, не явля­
ющиеся идентификаторами, то это выражение следует заключить в фигур­
ные скобки, а открывающая фигурная скобка должна ставиться сразу же
после знака доллара, например:
scala> s"The answer is ${6 * 7}."
val res0: String = The answer is 42.

Scala содержит еще два интерполятора строк: raw и f. Интерполятор строк
raw ведет себя практически так же, как и s, за исключением того, что не рас­
познает управляющие последовательности символьных литералов (те самые,
которые показаны в табл. 5.2). Например, следующая инструкция выводит
четыре, а не два обратных слеша:
println(raw"No\\\\escape!") // выводит: No\\\\escape!

Интерполятор строк f позволяет прикреплять к встроенным выражениям
инструкции форматирования в стиле функции printf. Инструкции ставятся
после выражения и начинаются со знака процента (%), при этом используется
синтаксис, заданный классом java.util.Formatter. Например, вот как можно
было бы отформатировать число π:
scala> f"${math.Pi}%.5f"
val res1: String = 3.14159

Если для встроенного выражения не указать никаких инструкций форма­
тирования, то интерполятор строк f по умолчанию превратится в %s, что
означает подстановку значения, полученного в результате выполнения ме­
тода toString, точно так же, как это делает интерполятор строк s, например:
scala> val pi = "Pi"
val pi: String = Pi
scala> f"$pi is approximately ${math.Pi}%.8f."
val res2: String = Pi is approximately 3.14159265.

В Scala интерполяция строк реализуется перезаписью кода в ходе ком­
пиляции. Компилятор в качестве выражения интерполятора строк будет

112   Глава 5



Основные типы и операции

рассматривать любое выражение, состоящее из идентификатора, за которым
сразу же стоят открывающие двойные кавычки строкового литерала. Интер­
поляторы строк s, f и raw реализуются с помощью этого общего механизма.
Библиотеки и пользователи могут определять другие интерполяторы строк,
применяемые в иных целях.

5.4. Все операторы являются методами
Для основных типов Scala предоставляет весьма богатый набор операторов.
Как упоминалось в предыдущих главах, эти операторы — всего лишь прият­
ный синтаксис для обычных вызовов методов. Например, 1 + 2 означает то же
самое, что и 1.+(2). Иными словами, в классе Int имеется метод по имени +,
который получает Int-значение и возвращает Int-результат. Он вызывается
при сложении двух Int-значений:
val sum = 1 + 2 // Scala вызывает 1.+(2)

Чтобы убедиться в этом, можете набрать выражение, в точности соответ­
ствующее вызову метода:
scala> val sumMore = 1.+(2)
val sumMore: Int = 3

Фактически в классе Int содержится несколько перегруженных методов +,
получающих различные типы параметров1. Например, у Int есть еще один
метод, тоже по имени +, который получает и возвращает значения типа Long.
При сложении Long и Int будет вызван именно этот альтернативный метод:
scala> val longSum = 1 + 2L // Scala вызывает 1.+(2L)
val longSum: Long = 3

Символ + — оператор, точнее, инфиксный оператор. Форма записи опера­
торов не ограничивается методами, подобными +, которые в других языках
выглядят как операторы. Любой метод может использоваться в нотации
операторов, если он принимает только один параметр2. Например, в классе
String есть метод indexOf, получающий один параметр типа Char. Метод
indexOf ведет поиск первого появления в строке указанного символа и воз­
1

2

Перегруженные методы имеют точно такие же имена, но используют другие типы
аргументов. Более подробно перегрузка методов рассматривается в разделе 6.11.
В будущих версиях Scala методы с несимвольными именами будут разрешены
в качестве операторов только в том случае, если они объявлены с модификато­
ром infix.

5.4. Все операторы являются методами   113

вращает его индекс или –1, если символ найден не будет. Метод indexOf
можно использовать как оператор:
scala> val s = "Hello, world!"
val s: String = Hello, world!
scala> s indexOf 'o' // Scala вызывает s.indexOf('o')
val res0: Int = 4

Любой однопараметрический метод может быть оператором

В Scala операторы не относятся к специальному синтаксису языка.
Любой метод, который содержит один параметр, может быть операто­
ром. Однопараметрический метод становится оператором в зависимо­
сти от того, как вы его используете. Если вы напишете s.indexOf('o'),
то indexOf не будет являться оператором, но станет им, если запись
будет иметь вид формы оператора — s indexOf 'o'.
До сих пор рассматривались только примеры инфиксной формы записи
операторов, означающей, что вызываемый метод находится между объектом
и параметром или параметрами, которые нужно передать методу, как в вы­
ражении 7 + 2. В Scala также имеются две другие формы записи операторов:
префиксная и постфиксная. В префиксной форме записи имя метода ста­
вится перед объектом, в отношении которого вызывается этот метод (напри­
мер, – в выражении –7). В постфиксной форме имя метода ставится после
объекта (например, toLong в выражении 7 toLong).
В отличие от инфиксной формы записи, в которой операторы получают два
операнда (один слева, другой справа), префиксные и постфиксные операторы
являются унарными — получают только один операнд. В префиксной форме
записи операнд размещается справа от оператора. В качестве примеров мож­
но привести выражения –2.0, !found и ~0xFF. Как и в случае использования
инфиксных операторов, эти префиксные операторы являются сокращенной
формой вызова методов. Но в данном случае перед символом оператора в име­
ни метода ставится приставка unary_. Например, Scala превратит выражение
–2.0 в вызов метода (2.0).unary_–. Вы можете убедиться в этом, набрав вызов
метода как с использованием формы записи операторов, так и в явном виде:
scala> -2.0 // Scala вызывает (2.0).unary_val res2: Double = -2.0
scala> (2.0).unary_val res3: Double = -2.0

114   Глава 5



Основные типы и операции

Идентификаторами, которые могут служить в качестве префиксных операто­
ров, являются только +, –, ! и ~. Следовательно, если вы определите метод по
имени unary_!, то сможете вызвать его в отношении значения или переменной
подходящего типа, прибегнув к префиксной форме записи операторов, напри­
мер !p. Но, определив метод по имени unary_*, вы не сможете использовать
префиксную форму записи операторов, поскольку * не входит в число четы­
рех идентификаторов, которые могут использоваться в качестве префиксных
операторов. Метод можно вызвать обычным способом как p.unary_*, но при
попытке вызвать его в виде *p Scala воспримет код так, словно он записан
в виде *.p, что, вероятно, совершенно не совпадает с задуманным1!
Постфиксные операторы, будучи вызванными без точки или круглых ско­
бок, являются методами, не получающими аргументов. В Scala при вызове
метода пустые круглые скобки можно не ставить. Соглашение гласит, что
круглые скобки ставятся, если метод имеет побочные эффекты, как в случае
с методом println(). Но их можно не ставить, если метод не имеет побочных
эффектов, как в случае с методом toLowerCase, вызываемым в отношении
значения типа String:
scala> val s = "Hello, world!"
val s: String = Hello, world!
scala> s.toLowerCase
val res4: String = hello, world!

В последнем случае, где методу не требуются аргументы, можно при желании
не ставить точку и воспользоваться постфиксной формой записи операто­
ров. Однако компилятор потребует, чтобы вы импортировали scala.lan­
guage.postfixOps, прежде чем вызывать метод:
scala> import scala.language.postfixOps
scala> s toLowerCase
val res5: String = hello, world!

Здесь метод toLowerCase используется в качестве постфиксного оператора
в отношении операнда s.
Чтобы понять, какие операторы можно использовать с основными типами
Scala, нужно посмотреть на методы, объявленные в классах типов, в доку­
ментации по Scala API. Но данная книга — пособие по языку Scala, поэтому
в нескольких следующих разделах будет представлен краткий обзор боль­
шинства этих методов.
1

Однако не обязательно все будет потеряно. Есть весьма незначительная вероят­
ность того, что программа с кодом *p может скомпилироваться как код C++.

5.5. Арифметические операции    115

УСКОРЕННЫЙ РЕЖИМ ЧТЕНИЯ ДЛЯ JAVA-ПРОГРАММИСТОВ
Многие аспекты Scala, рассматриваемые в оставшейся части главы, совпадают с аналогичными Java-аспектами. Если вы хорошо разбираетесь
в Java и у вас мало времени, то можете спокойно перейти к разделу 5.8,
в котором рассматриваются отличия Scala от Java в области равенства
объектов.

5.5. Арифметические операции
Арифметические методы при работе с любыми числовыми типами можно
вызвать в инфиксной форме для сложения (+ ), вычитания (– ), умноже­
ния (* ), деления (/ ) и получения остатка от деления (% ). Вот несколько
примеров:
1.2 + 2.3
3 — 1
'b' — 'a'
2L * 3L
11 / 4
11 % 4
11.0f / 4.0f
11.0 % 4.0

//
//
//
//
//
//
//
//

3.5: Double
2: Int
1: Int
6: Long
2: Int
3: Int
2.75: Float
3.0: Double

Когда целочисленными типами являются как правый, так и левый операнды
(Int, Long, Byte, Short или Char), оператор / выведет всю числовую часть
результата деления, исключая остаток. Оператор % показывает остаток от
предполагаемого целочисленного деления.
Остаток от деления числа с плавающей точкой, полученный с помощью
метода %, не определен в стандарте IEEE 754. Что касается операции вы­
числения остатка, в этом стандарте используется деление с округлением,
а не деление с отбрасыванием остатка. Поэтому данная операция сильно от­
личается от операции вычисления остатка от целочисленного деления. Если
все-таки нужно получить остаток по стандарту IEEE 754, то можно вызвать
метод IEEEremainder из scala.math:
math.IEEEremainder(11.0, 4.0) // -1.0: Double

Числовые типы также предлагают прибегнуть к унарным префиксным
операторам + (метод unary_+) и – (метод unary_–), позволяющим показать
положительное или отрицательное значение числового литерала, как в –3
или +4.0. Если не указать унарный + или –, то литерал интерпретируется
как положительный. Унарный + существует исключительно для симметрии
с унарным –, однако не производит никаких действий. Унарный – может

116   Глава 5



Основные типы и операции

также использоваться для смены знака переменной. Вот несколько при­
меров:
val neg = 1 + -3
val y = +3
-neg

// -2 : Neg
// 3: Int
// 2: Int

5.6. Отношения илогические операции
Числовые типы можно сравнивать с помощью методов отношений «боль­
ше» (>), «меньше» (=) и «меньше или равно» ( 2
1 < 2
1.0 = 3.6f
'a' >= 'A'
val untrue = !true

//
//
//
//
//
//

false: Boolean
true: Boolean
true: Boolean
false: Boolean
true: Boolean
false: Boolean

Методы «логическое И» (&& и &) и «логическое ИЛИ» (|| и |) получают
операнды типа Boolean в инфиксной нотации и выдают результат в виде
Boolean-значения, например:
val toBe = true
// true: Boolean
val question = toBe || !toBe // true: Boolean
val paradox = toBe && !toBe // false: Boolean

Операции && и ||, как и в Java, — сокращенно вычисляемые: выражения, по­
строенные с помощью этих операторов, вычисляются, только когда это нуж­
но для определения результата. Иными словами, правая часть выражений
с использованием && и || не будет вычисляться, если результат уже опреде­
лился при вычислении левой части. Например, если левая часть выражения
с методом && вычисляется в false, то результатом выражения, несомненно,
будет false, поэтому правая часть не вычисляется. Аналогично этому если
левая часть выражения с методом || вычисляется в true, то результатом
выражения конечно же будет true, поэтому правая часть не вычисляется:
scala> def salt() = { println("salt"); false }
def salt(): Boolean
scala> def pepper() = { println("pepper"); true }
def pepper(): Boolean

5.7. Поразрядные операции   117

scala> pepper() && salt()
pepper
salt
val res21: Boolean = false
scala> salt() && pepper()
salt
val res22: Boolean = false

В первом выражении вызываются pepper и salt, но во втором вызывается
только salt. Поскольку salt возвращает false, то необходимость в вызове
pepper отпадает.
Если правую часть нужно вычислить при любых условиях, то вместо пока­
занных выше методов следует обратиться к методам & и |. Первый выполняет
логическую операцию И, а второй — операцию ИЛИ, но при этом они не
прибегают к сокращенному вычислению, как это делают методы && и ||. Вот
как выглядит пример их использования:
scala> salt() & pepper()
salt
pepper
val res23: Boolean = false

ПРИМЕЧАНИЕ
Возникает вопрос: как сокращенное вычисление может работать, если
операторы — всего лишь методы? Обычно все аргументы вычисляются
перед входом в метод, тогда каким же образом метод может избежать вычисления своего второго аргумента? Дело в том, что у всех методов Scala
есть средство для задержки вычисления его аргументов или даже полной
его отмены. Оно называется «параметр, передаваемый по имени» и будет
рассмотрено в разделе 9.5.

5.7. Поразрядные операции
Scala позволяет выполнять операции над отдельными разрядами целочис­
ленных типов, используя несколько поразрядных методов. К таким методам
относятся поразрядное И (&), поразрядное ИЛИ (|) и поразрядное исклю­
чающее ИЛИ (^)1. Унарный поразрядный оператор дополнения (~, метод
unary_~) инвертирует каждый разряд в своем операнде, например:
1

Метод поразрядного исключающего ИЛИ выполняет соответствующую опера­
цию в отношении своих операндов. Из одинаковых разрядов получается 0, а из
разных -1. Следовательно, выражение 0011 ^ 0101 вычисляется в 0110.

118   Глава 5



Основные типы и операции

1 & 2 // 0: Int
1 | 2 // 3: Int
1 ^ 3 // 2: Int
~1
// -2: Int

В первом выражении, 1 & 2, выполняется поразрядное И над каждым раз­
рядом чисел 1 (0001) и 2 (0010) и выдается результат 0 (0000). Во втором
выражении, 1 | 2, выполняется поразрядное ИЛИ над теми же операндами
и выдается результат 3 (0011). В третьем выражении, 1 ^ 3, выполняется
поразрядное исключающее ИЛИ над каждым разрядом 1 (0001) и 3 (0011)
и выдается результат 2 (0010). В последнем выражении, ~1, инвертируется
каждый разряд в 1 (0001) и выдается результат –2, который в двоичной
форме выглядит как 11111111111111111111111111111110.
Целочисленные типы Scala также предлагают три метода сдвига: влево () и беззнаковый сдвиг вправо (>>>). Методы сдвига, примененные
в инфиксной форме записи операторов, сдвигают разряды целочисленного
значения, указанные слева от оператора, на количество разрядов, указанное
в целочисленном значении справа от оператора. При сдвиге влево и беззнако­
вом сдвиге вправо разряды по мере сдвига заполняются нулями. При сдвиге
вправо разряды указанного слева значения по мере сдвига заполняются зна­
чением самого старшего разряда (разряда знака). Вот несколько примеров:
-1 >> 31 // -1: Int
-1 >>> 31 // 1: Int
1 > 31, в числе –1 происходит сдвиг вправо на 31 разряд­
ную позицию. В значении типа Int содержатся 32 разряда, поэтому данная
операция, по сути, перемещает самый левый разряд до тех пор, пока тот не
станет самым правым1. Поскольку метод >> выполняет заполнение по мере
сдвига единицами ввиду того, что самый левый разряд числа –1 — это 1,
результат получается идентичным исходному левому операнду и состоит
из 32 единичных разрядов, или равняется –1. Во втором примере, –1 >>> 31,
самый левый разряд опять сдвигается вправо в самую правую позицию, од­
нако на сей раз освобождающиеся разряды заполняются нулями. Поэтому
результат в двоичном виде получается 00000000000000000000000000000001,
или 1. В последнем примере, 1