Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 762
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Резюме 55
Существуют также изначально функциональные языки, которые приобрели систему объектов. В качестве примера можно привести OCaml, F# и PLT
Scheme.
В 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 и добавили их в переменную окружения path
1
, что необходимо для выполнения шагов из этой главы.
Если вы опытный программист, но новичок в Scala, то внимательно про
читайте следующие две главы: в них приводится достаточный объем ин
формации, позволяющий приступить к написанию полезных программ на этом языке. Если же опыт программирования у вас невелик, то часть материалов может показаться чемто загадочным. Однако не стоит пере
живать. Чтобы ускорить процесс изучения, нам пришлось обойтись без некоторых подробностей. Более обстоятельные пояснения мы представим в последующих главах. Кроме того, в следующих двух главах дадим ряд сносок с указанием разделов книги, в которых можно найти более по
дробные объяснения.
1
Мы протестировали примеры из этой книги со Scala версии 3.0.0.
Шаг 1 . Осваиваем Scala REPL 57
Шаг 1 . Осваиваем Scala REPL
Самый простой способ начать работу со Scala — использовать Scala REPL
1
, интерактивную оболочку для написания выражений и программ Scala. REPL, который называется scala
, оценивает введенные вами выражения и выво
дит полученное значение. Чтобы его использовать, нужно набрать scala в командной строке
2
:
$ scala
Starting Scala REPL...
scala>
После того как вы наберете выражение, например
1
+
2
, и нажмете клавишу
Enter
:
scala> 1 + 2
REPL выведет на экран:
val res0: Int = 3
Эта строка включает:
z z
ключевое слово val
, объявляющее переменную;
z z
автоматически сгенерированное или определенное пользователем имя для ссылки на вычисленное значение (
res0
, означающее результат 0);
z z
двоеточие (
:
), за которым следует тип выражения (
Int
);
z z
знак равенства (
=
);
z z
значение, полученное в результате вычисления выражения (
3
).
Тип
Int означает класс
Int в пакете scala
. Пакеты в Scala аналогичны пакетам в Java — они разбивают глобальное пространство имен на части и предоставляют механизм для сокрытия данных
3
. Значения класса
Int соответствуют int
значениям в Java. Если говорить в общем, то все прими
тивные типы Java имеют соответствующие классы в пакете scala
. Например, sca la.Boolean соответствует Javaтипу boolean
. А scala.Float соответствует
1
REPL означает read, evaluate, print, loop («чтение, оценка, печать, цикл»).
2
Если вы используете Windows, вам нужно будет ввести команду scala в оболочке командной строки.
3
Если вы не знакомы с пакетами 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
Простым именем java.lang.String является
String
2
Но в интерпретаторе новую 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
. Она определяет результирующий тип самой функции max
1
. За типом результата функции следует знак равенства и тело функции, которое отделено отступами. В этом случае тело содержит одно выражение 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
В Java тип возвращаемого из метода значения является возвращаемым типом.
В Scala то же самое понятие называется результирующим типом.
2
Функция называется рекурсивной, если вызывает саму себя.
3
Тем не менее зачастую есть смысл указывать результирующий тип явно, даже когда компилятор этого не требует. Такая аннотация типа может упростить чтение кода, поскольку читателю не придется изучать тело функции, чтобы определить, каким будет вывод результирующего типа.
1 2 3 4 5 6 7 8 9 10 ... 64
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*
, что означает от нуля до многих по
вторяющихся параметров типа
String
1
. Внутри основной функции параметр будет иметь тип
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.