Главная страница

Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


Скачать 6.24 Mb.
НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
Дата27.04.2023
Размер6.24 Mb.
Формат файлаpdf
Имя файлаScala. Профессиональное программирование 2022.pdf
ТипДокументы
#1094967
страница8 из 64
1   ...   4   5   6   7   8   9   10   11   ...   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.

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 <- args do println(arg)
Между for и do находится arg
<- args
1
. Справа от символа
<–
расположена уже знакомая вам последовательность args
. Слева от
<–
указана переменная arg
, относящаяся к val
­, а не к var
­переменным (так как она всегда относится к val
­переменным, записывается только arg
, а не val arg
). Может показаться, что arg относится к var
­переменной, поскольку она будет получать новое значение при каждой итерации, однако в действительности она относится к val
­переменной: arg не может получить новое значение внутри тела выра­
жения. Вместо этого для каждого элемента массива args будет создана новая val
­переменная по имени arg
, которая будет инициализирована значением элемента, и тело for будет выполнено.
Если скрипт forargs.scala запустить с помощью команды:
$ scala forargs.scala for arg in args то вы увидите:
for arg in args
Диапазон применения выражения for значительно шире, но для начала это­
го примера достаточно. Мы расскажем вам больше о for в шаге 12 главы 3 и в разделе 7.3.
Резюме
В данной главе мы привели основную информацию о Scala. Надеемся, вы воспользовались возможностью создать код на этом языке. В следующей главе мы продолжим вводный обзор и рассмотрим более сложные темы.
1
Вы можете интерпретировать символ
<–
как in
. Следовательно, выражение for arg
<–
args do можно прочитать как for arg in args do

3
Дальнейшие шаги в Scala
В этой главе продолжается введение в Scala, начатое в предыдущей главе.
Здесь мы рассмотрим более сложные функциональные возможности. Когда вы усвоите материал главы, у вас будет достаточно знаний, чтобы начать создавать полезные скрипты на Scala. Мы вновь рекомендуем по мере чтения текста получать практические навыки с помощью приводимых примеров.
Лучше всего осваивать Scala, начиная создавать код на данном языке.
Шаг 7 . Параметризуем массивы типами
В Scala создавать объекты или экземпляры класса можно с помощью клю­
чевого слова new
. При создании объекта в Scala вы можете параметризовать его значениями и типами. Параметризация означает «конфигурирование» экземпляра при его создании. Параметризация экземпляра значениями произ­
водится путем передачи конструктору объектов в круглых скобках. Например, код Scala, который показан ниже, создает новый объект java.math.BigInteger
, выполняя его параметризацию значением "12345"
:
val big = new java.math.BigInteger("12345")
Параметризация экземпляра типами выполняется с помощью указания одного или нескольких типов в квадратных скобках. Пример показан в ли­
стинге 3.1. Здесь greetStrings
— значение типа
Array[String]
(«массив строк»), инициализируемое длиной
3
путем его параметризации значе­
нием
3
в первой строке кода. Если запустить код в листинге 3.1 в качестве скрипта, то вы увидите еще одно приветствие
Hello,
world!
. Учтите, что при параметризации экземпляра как типом, так и значением тип стоит

70 Глава 3 • Дальнейшие шаги в Scala первым и указывается в квадратных скобках, а за ним следует значение в круглых скобках.
Листинг 3.1. Параметризация массива типом val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for i <- 0 to 2 do print(greetStrings(i))
ПРИМЕЧАНИЕ
Хотя код в листинге 3 .1 содержит важные понятия, он не показывает рекомендуемый способ создания и инициализации массива в Scala . Более рациональный способ будет показан в листинге 3 .2 .
Если вы склонны делать более явные указания, то тип greetStrings можно обозначить так:
val greetStrings: Array[String] = new Array[String](3)
С учетом имеющегося в Scala вывода типов эта строка кода семантически эквивалентна первой строке листинга 3.1. Но в данной форме показано сле­
дующее: часть параметризации, которая относится к типу (название типа в квадратных скобках), формирует часть типа экземпляра, однако часть параметризации, относящаяся к значению (значения в круглых скобках), в формировании не участвует. Типом greetStrings является
Array[String]
, а не
Array[String](3)
В следующих трех строках кода в листинге 3.1 инициализируется каждый элемент массива greetStrings
:
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
Как уже упоминалось, доступ к массивам в Scala осуществляется за счет по­
мещения индекса элемента в круглые, а не в квадратные скобки, как в Java.
Следовательно, нулевым элементом массива будет greetStrings(0)
, а не greetStrings[0]
Эти три строки кода иллюстрируют важное понятие, помогающее осмыс­
лить значение для Scala val
­переменных. Когда переменная определяется

Шаг 7 . Параметризуем массивы типами 71
с помощью val
, повторно присвоить значение данной переменной нельзя, но объект, на который она ссылается, потенциально может быть изменен.
Следовательно, в данном случае присвоить greetStrings значение другого массива невозможно — переменная greetStrings всегда будет указывать на один и тот же экземпляр типа
Array[String]
, которым она была инициали­
зирована. Но впоследствии в элементы типа
Array[String]
можно вносить изменения, то есть сам массив является изменяемым.
Последние две строки листинга 3.1 содержат выражение for
, которое по­
очередно выводит каждый элемент массива greetStrings
:
for i <- 0 to 2 do print(greetStrings(i))
В первой строке кода для этого выражения for показано еще одно общее пра­
вило Scala: если метод получает лишь один параметр, то его можно вызвать без точки или круглых скобок. В данном примере to на самом деле является методом, получающим один
Int
­аргумент. Код
0
to
2
преобразуется в вызов метода
0.to(2)
1
. Следует заметить, что этот синтаксис работает только при явном указании получателя вызова метода. Код println
10
использовать нельзя, а код
Console println
10
— можно.
С технической точки зрения в Scala нет перегрузки операторов, поскольку в нем фактически отсутствуют операторы в традиционном понимании.
Вместо этого такие символы, как
+
,
-
,
*
,
/
, могут использоваться в качестве имен методов. Следовательно, когда при выполнении шага 1 вы набираете в интерпретаторе Scala код
1
+
2
, в действительности вы вызываете метод по имени
+
в отношении
Int
­объекта
1
, передавая ему в качестве параметра значение
2
. Как показано на рис. 3.1, вместо этого
1
+
2
можно записать с по­
мощью традиционного синтаксиса вызова метода:
1.+(2)
Еще одна весьма важная идея, проиллюстрированная в этом примере, по­
может понять, почему доступ к элементам массивов Scala осуществляется с помощью круглых скобок. В Scala меньше особых случаев по сравнению с Java. Массивы в Scala, как и в случае с любыми другими классами, — просто экземпляры классов. При использовании круглых скобок, окру­
жающих одно или несколько значений переменной, Scala преобразует код в вызов метода по имени apply применительно к данной переменной.
1
Этот метод to фактически возвращает не массив, а иную разновидность последо­
вательности, содержащую значения 0, 1 и 2, последовательный перебор которых выполняется выражением for
. Последовательности и другие коллекции будут рассматриваться в главе 15.

1   ...   4   5   6   7   8   9   10   11   ...   64


написать администратору сайта