Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 773
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
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
1 2 3 4 5 6 7 8 9 ... 64
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 позволяет повышать ценность уже существующего кода, то есть опираться на то, что у вас уже есть, поскольку он был разработан для достижения беспрепятственной совместимости с Java
1
. Программы на 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
Изначально существовала реализация Scala, запускаемая на платформе .NET, но она больше не используется. В последнее время все большую популярность на
бирает реализация Scala под названием Scala.js, запускаемая на JavaScript.
2
В версии 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
) и конструктор, который получает исходные значения для этих переменных в виде параметров. Код данного конструктора проинициализи
рует две переменные экземпляра значениями, переданными в качестве па
раметров. Короче говоря, в итоге вы получите ту же функциональность, что и у более многословной версии кода на Java
1
. Класс в Scala быстрее пишется и проще читается, а еще — и это наиболее важно — допустить ошибку при его создании значительно труднее, чем при создании класса в Java.
Еще один фактор, способствующий лаконичности, — используемый в Scala вывод типов. Повторяющуюся информацию о типе можно отбросить, и тогда программы избавятся от лишнего и их легче будет читать.
Но, вероятно, наиболее важный аспект сокращения объема кода — наличие кода, не требующего внесения в программу, поскольку это уже сделано в би
блиотеке. Scala предоставляет вам множество инструментальных средств для определения эффективных библиотек, позволяющих выявить и вынести за скобки общее поведение. Например, различные аспекты библиотечных классов можно выделить в трейты, которые затем можно перемешивать про
извольным образом. Или же библиотечные методы могут быть параметризо
ваны с помощью операций, позволяя вам определять конструкции, которые, по сути, являются вашими собственными управляющими конструкциями.
Собранные вместе, эти конструкции позволяют определять библиотеки, со
четающие в себе высокоуровневый характер и гибкость.
Scala — высокоуровневый язык
Программисты постоянно борются со сложностью. Для продуктивного про
граммирования нужно понимать код, над которым вы работаете. Чрезмерно сложный код был причиной краха многих программных проектов. К сожа
лению, важные программные продукты обычно бывают весьма сложными.
Избежать сложности невозможно, но ею можно управлять.
Scala помогает управлять сложностью, позволяя повышать уровень абстрак
ции в разрабатываемых и используемых интерфейсах. Представим, к при
меру, что есть переменная name
, имеющая тип
String
, и нужно определить,
1
Единственное отличие заключается в том, что переменные экземпляра, полученные в случае применения Scala, будут финальными (final). Как сделать их не финаль
ными, рассказывается в разделе 10.6.