Scala в примерах: различия между версиями
Содержимое удалено Содержимое добавлено
Нет описания правки |
Oleg4280 (обсуждение | вклад) Категория:Scala плюс викификация |
||
Строка 1:
''Здесь ведется перевод книги
{{TOC right}}
Строка 16:
Главы [[Scala в примерах#Первый пример|2]] и [[Scala в примерах#Программирование с акторами и сообщениями|3]] обращают внимание на некоторые возможности, из-за которых Scala особенно интересен. Последующие главы представляют конструкты языка более подробно, начиная с простых выражений и функций, через объекты и классы, списки и потоки, изменяемые состояния, сопоставление с образцом, к более сложным примерам, иллюстрирующим интересные приемы программирования. Это неформальное изложение должно быть дополнено документом [http://www.scala-lang.org/docu/files/ScalaReference.pdf Scala Language Reference Manual], который специфицирует Scala более точно и детально.
'''Замечание.''' Мы в огромном долгу у чудесной книги Абельсона и Сассмана,
= Первый пример =
Строка 97:
Взглянув снова на первую, императивную реализацию быстрой сортировки, мы увидим там многие языковые конструкты из второго решения, хотя и в замаскированной форме.
Например,
Для эффективности и лучшего обнаружения ошибок цикл '''<tt>while</tt>''' представлен в Scala как примитивная конструкция языка. Но, в принципе, его можно было бы вынести в библиотеку, оформив как функцию. Вот возможная реализация:
Строка 112:
Функция <tt>While</tt> принимает первым параметром функцию проверки, которая не имеет аргументов, и возвращает булево значение. Второй параметр это командная функция, которая также не имеет аргументов и возвращает результат типа <tt>Unit</tt>. While вызывает командную функцию до тех пор, пока предикатная функция возвращает <tt>true</tt>.
Тип <tt>Unit</tt> в Scala, грубо говоря, соответствует <tt>void</tt> в Java; он используется всякий раз, когда функция не возвращает какого-либо интересного значения. На самом деле, поскольку Scala это язык, ориентированный на выражения, каждая функция возвращает некоторый результат. Если нет явно заданного выражения, то предполагается значение по умолчанию, <tt>()</tt> (произносится
<div style="width: 450px"><font size="3">
Строка 128:
= Программирование с акторами и сообщениями =
Вот пример, который демонстрирует область применения, для которой Scala особенно хорошо подходит. Рассмотрим задачу по созданию электронного аукциона. Для реализации участников аукциона мы используем модель акторных процессов в [http://erlang.org Erlang]-стиле. Акторы
Для каждого лота есть актор-аукционщик, который публикует информацию о лоте, принимает предложения от участников и общается с продавцом лота и победившим покупателем для завершения сделки.
Строка 204:
* Метод <tt>receiveWithin</tt> класса [http://www.daimi.au.dk/~eernst/dProgSprog06/share/doc/scala-1.4.0.4/api/scala/concurrent/Actor-class.html <tt>Actor</tt>] принимает в качестве параметров продолжительность торгов в миллисекундах и функцию, которая обрабатывает сообщения, поступающие в почтовый ящик. Функция задана последовательностью вариаций, каждая из которых определяет шаблон и действия, которые следует предпринять для сообщений, соответствующих этому шаблону. Метод <tt>receiveWithin</tt> выбирает из почтового ящика первое сообщение, которое соответствует одному из этих шаблонов и выполняет соответствующее действие.
* Последняя вариация в <tt>receiveWithin</tt>
* Ответные сообщения посылаются в форме <tt>destination ! SomeMessage</tt>. <tt>!</tt> используется здесь как бинарный оператор с аргументами: актором и сообщением. В Scala это равносильно вызову метода <tt>destination.!(SomeMessage)</tt>,
Предыдущее обсуждение показало возможности распределенного программирования на Scala. Может показаться, что в Scala есть богатый набор языковых конструкций, поддерживающих процессы-акторы, обмен сообщениями, программирование с тайм-аутами, и
Сам класс написан на Scala и базируется на модели многопоточности, используемой в нижележащей платформе (напр. Java или .NET). Все возможности класса <tt>Actor</tt>, использованные здесь, рассмотрены в [[Scala в примерах#Акторы|Параграфе 17.11]].
Преимущество подобного библиотечного подхода к многопоточности ''(см. также [http://lamp.epfl.ch/~cremet/publications/pilib.pdf PiLib]) ''
= Выражения и простые функции =
Строка 219:
== Выражения и простые функции ==
В дистрибутив Scala входит интерпретатор, который можно рассматривать как сложный калькулятор. Пользователь взаимодействует с калькулятором, вводя выражения. Калькулятор возвращает результаты вычислений и их типы. Например,
<font size=3><syntaxhighlight lang=Bash>
Строка 260:
* применить оператор к значениям операндов.
Имя, определенное при помощи <tt>'''def'''</tt>, вычисляется путем замены имени на (невычисленную) правую часть определения. Имя, определенное при помощи <tt>'''val'''</tt>, вычисляется путем замены имени на значение правой части. Процесс вычисления заканчивается, когда приходит к одному значению. Значение
'''Пример 4.1.1''' Здесь представлено вычисление арифметического выражения.
Строка 298:
</syntaxhighlight></font>
Параметры функций перечисляются после имени функции и всегда заключаются в скобки. Каждый параметр имеет тип, который указывается после имени параметра и двоеточия. Пока что нам потребуются только базовые числовые типы, такие как <tt>scala.Double</tt>
Функции с параметрами вычисляются аналогично операторам в выражениях. Сначала вычисляются аргументы функции (слева направо). Затем применение функции заменяется на ее правую часть, и в то же время все формальные параметры функции заменяются на соответствующие им фактические аргументы.
Строка 368:
== Условные выражения ==
<tt>'''if-else'''</tt> в Scala позволяет выбирать между двумя альтернативами. Синтаксис похож на <tt>'''if-else'''</tt> в Java. Но тогда как <tt>'''if-else'''</tt> в Java можно использовать только для альтернативных утверждений, Scala позволяет использовать тот же синтаксис для выбора между двумя выражениями. Поэтому в Scala <tt>'''if-else'''</tt> служит также для замены условного выражения Java
'''Пример 4.3.2'''
Строка 381:
== Пример: квадратные корни по методу Ньютона ==
Теперь проиллюстрируем элементы языка, которые мы упомянули к этому моменту, более интересной программой. Задание
<font size=3><syntaxhighlight lang=Scala>
Строка 389:
которая вычисляет квадратный корень <tt>x</tt>.
Обычный способ вычисления квадратных корней
{| class="simple" border="0"
Строка 565:
Между этими двумя последовательностями вывода есть важное различие: термы последовательности вывода <tt>gcd</tt> вновь и вновь принимают одну и ту же форму. В течение вычисления их длина ограничена константой. В вычислении факториала, напротив, мы получаем все более длинные цепи операндов, которые впоследствии перемножаются в последней части последовательности вычислений.
Несмотря на то, что на самом деле Scala не переписывает термы, затраты по памяти будут такими же, что и в последовательности вывода. Заметим, что в реализации <tt>gcd</tt> рекурсивный вызов <tt>gcd</tt>
Рекурсивый вызов <tt>factorial</tt>, напротив, заканчивается операцией умножения. Отметим, что для рекурсивного вызова <tt>factorial</tt> выделяется новый кадр стека, который стирается после завершения выполнения вызова. Данная реализации функции факториала не хвостово-рекурсивна, и ей необходимо количество памяти, пропорциональное размеру ввода.
Строка 573:
'''Упражнение 4.6.1''' Придумайте хвостово-рекурсивную версию функции <tt>factorial</tt>.
= Функции первого класса
Функции в Scala являются
Для мотивации рассмотрим следующие три задания.
Строка 594:
</syntaxhighlight></font>
3. Написать функцию для суммирования степеней двойки ''2<sup>n</sup>'' для
<font size=3><syntaxhighlight lang=Scala>
Строка 609:
</syntaxhighlight></font>
Тип <tt>Int => Int</tt>
Используя <tt>sum</tt>, мы можем сформулировать три суммирующие функции так:
Строка 637:
</syntaxhighlight></font>
Часть перед стрелкой
<font size=3><syntaxhighlight lang=Scala>
Строка 657:
</syntaxhighlight></font>
Выражение Scala <tt>(x<sub>1</sub>: T<sub>1</sub>, …, x<sub>n</sub>: T<sub>n</sub>) => E</tt> определяет функцию, которая соотносит свои параметры <tt>x<sub>1</sub>, …, x<sub>n</sub></tt> с результатами выражения <tt>E</tt> (где <tt>E</tt> может ссылаться на <tt>x<sub>1</sub>, …, x<sub>n</sub></tt>). Анонимные функции
<code>
Строка 669:
</code>
где <tt>f</tt>
== Каррирование ==
Строка 685:
</syntaxhighlight></font>
В этой формулировке <tt>sum</tt>
При помощи этой формулировки <tt>sum</tt> мы можем теперь определить:
Строка 718:
функция <tt>sum</tt> применяется к функции возведения в квадрат <tt>(x => x * x)</tt>. Получившаяся функция затем применяется ко второму списку аргументов <tt>(1, 10)</tt>.
Такая запись возможна, потому что применение функций левоассоциативно. То есть, если args<sub>1</sub> и args<sub>2</sub>
В нашем примере <tt>sum(x => x * x)(1, 10)</tt> эквивалентно следующему выражению: <tt>(sum(x => x * x))(1, 10)</tt>.
Строка 741:
</code>
где <tt>g</tt>
<code>
Строка 767:
Такой стиль определения и применения функций называется ''каррирование'' (currying) в честь своего популяризатора Хаскелла Карри, логика XX века, хотя идея принадлежит Мозесу Шонфинкелю и Готтлобу Фреге.
Тип функции, возвращающей функцию, выражается аналогично списку ее параметров. Возьмем к примеру последнюю формулировку <tt>sum</tt>. Тип <tt>sum</tt>
'''Упражнение 5.2.1''' 1. Функция <tt>sum</tt> использует линейную рекурсию. Напишите хвостоворекурсивный вариант, заполнив ??.
Строка 808:
Теперь применим эту идею при формулировании функции вычисления квадратных корней. Начнем со спецификации <tt>sqrt</tt>: <tt> sqrt(x) = the ''y'' such that y * y = x = the ''y'' such that y = x / y</tt>.
Заметьте, что <tt>sqrt(x)</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 875:
== Заключение ==
В предыдущей главе мы видели, что функции
== Использованные элементы языка ==
Строка 884:
<font size=3>'''Символы'''</font>
Программы Scala
* пробел, табуляция или символ перевода строки,
* буквы от ‘<tt>a</tt>’ до ‘<tt>z</tt>’, ‘<tt>A</tt>’ до ‘<tt>Z</tt>’,
Строка 899:
| идентификатор '_' идентификатор
литерал = "как в Java"
</pre>
Литералы
Идентификаторы могут быть двух видов. Они начинаются либо с буквы, за которой идет (возможно, пустая) последовательность букв и символов, либо с операторного символа, за которым идет (возможно, пустая) последовательность операторных символов. Оба вида идентификаторов могут содержать символы подчеркивания. За символом подчеркивания могут идти идентификатры любого типа. Например, следующие идентификатры корректны: <tt>x Room10a + -- foldl_: +_vector</tt>.
Строка 1046:
</syntaxhighlight></font>
Это определяет <tt>Rational</tt> как класс, принимающий два аргумента конструктора <tt>n</tt> и <tt>d</tt>, содержащие числитель и знаменатель числа. Класс предоставляет поля, возвращающие эти части, и методы для арифметических операций с рациональными числами. Левый операнд операции
Строка 1073:
<font size=3>'''Наследование и переопределение'''</font>
У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип
<font size=3><syntaxhighlight lang=Scala>
Строка 1110:
<font size=3>'''Методы без параметров'''</font>
В отличие от Java, методы в Scala не обязательно должны принимать список параметров. Пример тому
<font size=3><syntaxhighlight lang=Scala>
Строка 1174:
</syntaxhighlight></font>
И <tt>EmptySet</tt>, и <tt>NonEmptySet</tt> расширяют класс <tt>IntSet</tt>. Из этого следует, что типы <tt>EmptySet</tt> и <tt>NonEmptySet</tt> соответствуют типу <tt>IntSet</tt>
'''Упражнение 6.0.1''' Напишите метод <tt>union</tt> и <tt>intersection</tt> для формирования объединения и пересечения двух множеств.
Строка 1234:
</syntaxhighlight></font>
Проблема этого подхода в том, что такое определение значения нелегально для высшего уровня в Scala, оно должно быть частью
<font size=3><syntaxhighlight lang=Scala>
Строка 1250:
<font size=3>'''Стандартные классы'''</font>
Scala
<font size=3><syntaxhighlight lang=Scala>
Строка 1259:
</syntaxhighlight></font>
Для эффективности компилятор обычно представляет значения типа <tt>scala.Int</tt> 32-битными целыми числами, значения <tt>scala.Boolean</tt>
Вот спецификация класса <tt>Boolean</tt>:
Строка 1330:
</syntaxhighlight></font>
Класс <tt>Int</tt> можно в принципе реализовать, используя только объекты и классы, без ссылок на встроенный тип целых чисел. Чтобы понять, как это сделать, рассмотрим более простую задачу реализации типа <tt>Nat</tt> натуральных (
<font size=3><syntaxhighlight lang=Scala>
Строка 1479:
Впрочем, определять все эти методы в классах <tt>Number</tt> и <tt>Sum</tt> довольно утомительно. Более того, проблема становится еще хуже, если мы хотим добавить новые формы выражений. Например, рассмотрим добавление новой формы выражения <tt>Prod</tt> со всей предыдущей классификацией и методами доступа, мы также хотим ввести новый абстрактный метод <tt>isProduct</tt> в класс <tt>Expr</tt> и реализовать этот метод в подклассах <tt>Number</tt>, <tt>Sum</tt> и <tt>Prod</tt>. Необходимость модифицировать существующий код, когда система растет, всегда проблематично, поскольку это затрудняет контроль версий и поддержку.
Объектно-ориентированное программирование обещает, что такие модификации не обязательны, поскольку их можно избежать, переиспользуя существующий неизменяющийся код через механизм наследования. Действительно, более объектно-ориентированная организация решает проблему. Идея состоит в том, чтобы сделать операцию
<font size=3><syntaxhighlight lang=Scala>
Строка 1500:
</syntaxhighlight></font>
Из этого примера мы можем сделать вывод, что объектно-ориентированная организация
Если мы определили классификацию и методы доступа, такую операцию можно легко написать в виде внешней функции. Вот пример:
Строка 1548:
== Case-классы и case-объекты ==
''Case-классы'' и ''case-объекты'' (вариации) определяются как обычные классы и объекты, за исключением того, что перед определением ставится модифиатор <tt>'''case'''</tt>. Например, определения
<font size=3><syntaxhighlight lang=Scala>
Строка 1571:
</syntaxhighlight></font>
2. У case-классов и case-объектов есть неявные реализации методов <tt>toString</tt>, <tt>equals</tt> и <tt>hashCode</tt>, которые переписывают одноименные методы класса <tt>AnyRef</tt>. Реализация этих методов принимает во внимание структуру членов case-класса в каждом случае. Метод <tt>toString</tt> представляет дерево выражения так, как оно было сконструировано. Поэтому
<font size=3><syntaxhighlight lang=Scala>
Строка 1591:
</syntaxhighlight></font>
который возвращает параметр конструктора <tt>n</tt>, а у <tt>Sum</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 1597:
</syntaxhighlight></font>
Заметьте, что для значения <tt>s</tt> типа <tt>Sum</tt> теперь можно писать <tt>s.e1</tt> для доступа к левому операнду. Но для значения <tt>e</tt> типа <tt>Expr</tt> выражение <tt>e.e1</tt> будет некорректно, потому что <tt>e1</tt> определен в <tt>Sum</tt> и не является методом класса <tt>Expr</tt>. Итак, как определить конструктор и получить доступ к аргументам конструктора для значений, чей статический тип
4. Сase-классы разрешают конструкцию ''образцов'' (patterns), которые относятся к конструкторам case-классов.
Строка 1612:
</syntaxhighlight></font>
В этом примере есть два случая. Каждый случай сопоставляет образец с выражением. Образцы сопоставляются со значениями селектора <tt>e</tt>. Первый образец в нашем примере, <tt>Number(n)</tt>, соответствует всем значениями вида <tt>Number(v)</tt>, где <tt>v</tt>
В общем, образцы строятся из:
Строка 1707:
<font size=3>'''Анонимные функции сопоставления с образцом'''</font>
До этого case-выражения всегда появлялись вместе с оператором <tt>match</tt>. Но можно также использовать case-выражения сами по себе. Блок case-выражений, такой как
<code>
Строка 1713:
</code>
это функция, которая сопоставляет свои аргументы с образцами ''P<sub>1</sub>, …, P<sub>n</sub>'', и возвращает одно из выражений ''E<sub>1</sub>, …, E<sub>n</sub>''. (Если ни один образец не подошел, функция вместо этого выбросит исключение <tt>MatchError</tt>). Другими словами, выражение сверху это короткая запись анонимной функции
<code>
Строка 1723:
= Обобщенные типы и методы =
Классы в Scala могут иметь типовые параметры. Мы продемонстрируем использование типовых параметров на примере функциональных стеков. Скажем, нам нужно написать тип данных для стеков целых чисел с методами <tt>push</tt>, <tt>top</tt>, <tt>pop</tt> и <tt>isEmpty</tt>. Это можно сделать при помощи следующей иерархии классов:
<font size=3><syntaxhighlight lang=Scala>
Строка 1771:
</syntaxhighlight></font>
В данном определении '<tt>A</tt>' это ''типовой параметр'' класса <tt>Stack</tt> и его подклассов. Типовые параметры это произвольные имена; они заключены в квадратные, а не круглые скобки, чтобы их было просто отличить от параметров значений. Вот пример использования обобщенного класса:
<font size=3><syntaxhighlight lang=Scala>
Строка 1790:
</syntaxhighlight></font>
Параметры этого метода называются ''полиморфными''. Обобщенные методы также называются ''полиморфными''. Этот термин пришел из греческого языка, на котором он значит
<font size=3><syntaxhighlight lang=Scala>
Строка 1819:
</syntaxhighlight></font>
Первый способ решить эту проблему
<font size=3><syntaxhighlight lang=Scala>
Строка 1850:
</syntaxhighlight></font>
Обозначение параметра <tt>A <: Ordered[A]</tt> говорит, что <tt>A</tt> это типовой параметр, который должен быть подтипом <tt>Ordered[A]</tt>,
С этим ограничением мы можем реализовать остальные компоненты абстракции обобщенного множества, как мы делали в случае <tt>IntSet</tt> ранее:
Строка 1902:
Проблема, связанная с ограничениями типовых параметров, состоит в том, что их нужно обдумывать заранее: если бы мы не объявили, что <tt>Num</tt> это подкласс <tt>Ordered</tt>, мы бы не смогли использовать во множествах элементы типа <tt>Num</tt>. По той же причине типы, унаследованные из Java, такие, как <tt>Int</tt>, <tt>Double</tt> или <tt>String</tt> не являются подклассами <tt>Ordered</tt>, поэтому значения этих типов не могут быть использованы во множествах.
Более гибкий вариант, который допускает элементы этих типов, использует ''ограничения по видимости'' (view bounds) вместо простых ограничений по типам, которые мы видели раньше. Единственное изменение, которое нужно будет внести в наш пример,
<font size=3><syntaxhighlight lang=Scala>
Строка 1965:
Пока все в порядке. Интуитивно, компилятор был прав, запретив процедуру <tt>update</tt> в ковариантном классе, потому что <tt>update</tt> может изменить состояние, и поэтому ставит под угрозу корректность ковариантного подтипирования.
Также существуют методы, которые не меняют состояние, но в которых типовой параметр тоже появляется контравариантно. Пример тому
<font size=3><syntaxhighlight lang=Scala>
Строка 1975:
</syntaxhighlight></font>
Жаль, что так происходит, потому что, в отличие от массивов, стеки
== Нижние границы ==
Строка 2002:
</syntaxhighlight></font>
Нижний граничный тип <tt>Nothing</tt> не содержит значения, так что тип <tt>Stack[Nothing]</tt> выражает факт того, что в <tt>EmptyStack</tt> нет элементов. Помимо этого, <tt>Nothing</tt> является подтипом всех других типов. Заметьте, что для ковариантных стеков <tt>Stack[Nothing]</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 2014:
</syntaxhighlight></font>
Локальный вывод типов определит, что типовой параметр <tt>B</tt> должен быть <tt>String</tt> в применении <tt>EmptyStack.push("abc")</tt>. Результирующий тип этого применения
<font size=3><syntaxhighlight lang=Scala>
push[B >: String](elem: B): Stack[B]
</syntaxhighlight></font>
Последняя часть определения значения, приведенного выше, это применение этого метода к <tt>'''new''' AnyRef()</tt>. Локальный вывод типов определит, что типовой параметр <tt>B</tt> должен на этот раз быть <tt>AnyRef</tt>, с результирующим типом <tt>Stack[AnyRef]</tt>. То есть, тип, присвоенный значению <tt>s</tt>
Помимо <tt>Nothing</tt>, который является подтипом всех других типов, есть также тип <tt>Null</tt>, являющийся подтипом <tt>scala.AnyRef</tt> и всех классов, наследующих от него. Литерал <tt>'''null'''</tt> в Scala
Мы завершим этот параграф полным улучшенным определением стеков. Теперь у них ковариантное подтипирование, метод <tt>push</tt> обобщен, и пустой стек представлен единственным объектом.
<font size=3><syntaxhighlight lang=Scala>
abstract class Stack[+A] {
Строка 2045:
</syntaxhighlight></font>
Многие классы в библиотеке Scala
== Кортежи ==
Строка 2071:
Как обычно, типовые параметры конструкторов можно опустить, если они выводимы из аргументов. Есть также кортежные классы для любого другого количества элементов (нынешняя реализация Scala ограничивает кортежи по длине некоторым большим числом).
Как обращаться к элементам кортежей? Поскольку кортежи
<font size=3><syntaxhighlight lang=Scala>
Строка 2087:
</syntaxhighlight></font>
Заметьте, что типовые параметры никогда не используются в образцах, писать <tt>caseTuple2[Int, Int](n, d)</tt>
Кортежи так удобны, что у Scala есть для них специальный синтаксис. Чтобы сформировать кортеж с ''n'' элементами ''x<sub>1</sub>, …, x<sub>n</sub>'', можно написать ''(x<sub>1</sub>, …, x<sub>n</sub>)''. Такая запись эквивалентна <tt>Tuple</tt>''n(x<sub>1</sub>, …, x<sub>n</sub>)''. Синтаксис (…) работает одинаково для кортежей и паттернов. С таким кортежным синтаксисом пример <tt>divmod</tt> можно записать так:
Строка 2100:
== Функции ==
Scala
<font size=3><syntaxhighlight lang=Scala>
Строка 2109:
</syntaxhighlight></font>
Помимо <tt>Function1</tt> есть также определения для функций любой другой арности (нынешняя реализация устанавливает некий разумный предел). То есть, для любого возможного числа параметров есть соответствующее определение. Синтаксис типа функции в Scala <tt>(T<sub>1</sub>, …, T<sub>n</sub>) => S</tt>
Scala использует одинаковый синтаксис ''f(x)'' для применения функции, вне зависимости от того, является ли ''f'' методом или функциональным объектом. Это возможно благодаря следующему соглашению: применение функции ''f(x)'', где ''f''
Поэтому мы определяли операцию взятия элемента из массива в [[Scala в примерах#Аннотации вариантности|Параграфе 8.2]] при помощи метода <tt>apply</tt>. Для любого массива <tt>a</tt> операция взятия элемента <tt>a(i)</tt> это сокращенная форма записи <tt>a.apply(i)</tt>.
Функции
<font size=3><syntaxhighlight lang=Scala>
Строка 2123:
</syntaxhighlight></font>
Присваивать значение <tt>g</tt> типа <tt>String => Int</tt> величине <tt>f</tt> типа <tt>AnyRef => Int</tt>
'''Пример 8.6.1''' Рассмотрим код:
Строка 2154:
= Списки =
Списки
<font size=3><syntaxhighlight lang=Scala>
Строка 2165:
Списки похожи на массивы в таких языках, как C или Java, но есть три важных отличия. Во-первых, списки неизменяемы. То есть, элементы списка нельзя менять при помощи присваиваний. Во-вторых, у списков рекурсивная структура, тогда как массивы плоские. В третьих, списки поддерживают гораздо больше операций, чем обычные массивы.
== Использование списков ==
Строка 2170 ⟶ 2171 :
<font size=3>'''Тип List'''</font>
Как и массивы, списки ''гомогенны''. Это значит, все элементы списка
<font size=3><syntaxhighlight lang=Scala>
Строка 2182 ⟶ 2183 :
<font size=3>'''Конструкторы списков'''</font>
Все списки строятся при помощи двух более фундаментальных конструкторов, <tt>Nil</tt> и <tt>::</tt> (произвносится
<font size=3><syntaxhighlight lang=Scala>
Строка 2203 ⟶ 2204 :
Все операции со списками можно выразить при помощи следующих трех конструкций:
* <tt>head</tt>
* <tt>tail</tt>
* <tt>isEmpty</tt>
Эти операции определены как методы объектов-списков. Их можно вызвать, выбрав из списка, с которым мы работаем. Примеры:
Строка 2219 ⟶ 2220 :
Методы <tt>head</tt> и <tt>tail</tt> определены только для непустых списокв. Когда они выбраны из пустого списка, они выбрасывают исключение.
В качестве примера работы со списками рассмотрим сортировку элементов списка числе в возрастающем порядке. Простой способ сделать это
<font size=3><syntaxhighlight lang=Scala>
Строка 2259 ⟶ 2260 :
</syntaxhighlight></font>
<tt>List</tt>
Строка 2299 ⟶ 2300 :
</syntaxhighlight></font>
<tt>xs.last</tt> возвращает последний элемент списка <tt>xs</tt>, а <tt>xs.init</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 2331 ⟶ 2332 :
</syntaxhighlight></font>
У метода <tt>apply</tt> в Scala специальное значение. Объект с этим методом может быть применен к аргументам, как если бы он был функцией. Например, чтобы выбрать третий элемент списка <tt>xs</tt>, можно написать или <tt>xs.apply(3)</tt>, или <tt>xs(3)</tt>
С <tt>take</tt> и <tt>drop</tt> мы можем создавать списки, состоящие их последовательных элементов данного списка. Чтобы создать список из элементов ''xs<sub>m</sub>, …, xs<sub>n-1</sub>'' списка <tt>xs</tt>, используйте
Строка 2353 ⟶ 2354 :
<font size=3>'''Продолжение списка'''</font>
Как и любой инфиксный оператор, <tt>::</tt> также реализован как метод объекта. В этом случае объект
Заметьте, однако, что операнды бинарной операции в каждом случае вычисляются слева направо. Так что, если <tt>D</tt> и <tt>E</tt>
Другое отличие между операторами, заканчивающимися на ‘<tt>:</tt>’, и другими операторами относится к их ассоциативности. Операторы, заканчивающиеся на ‘<tt>:</tt>’, правоассоциативны, тогда как остальные
Определение <tt>::</tt> как метода класса <tt>List</tt>:
Строка 2365 ⟶ 2366 :
</syntaxhighlight></font>
Заметьте, что <tt>::</tt> определен для всех элементов x типа B и списков типа <tt>List[A]</tt>, таких что тип <tt>B</tt>
<font size=3>'''Конкатенация списков'''</font>
Операция конкатенации похожа на <tt>::</tt> и записывается <tt>:::</tt>. Результат выполнения <tt>(xs ::: ys)</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 2398 ⟶ 2399 :
</syntaxhighlight></font>
Эта реализация хороша тем, что она проста, но она не очень эффективна. Действительно, конкатенация выполняется для каждого элемента списка. Конкатенация списка занимает время, пропорциональное длине первого операнда. Следовательно, сложность <tt>reverse(xs)</tt>: ''n + (n
== Пример: сортировка слиянием ==
Представленную ранее в этой главе сортировку вставками просто сформулировать, но она не очень эффективна. Ее средняя сложность пропорциональна квадрату длины входного списка. Теперь мы разработаем программу для сортировки элементов списка, которая более эффективна. Хороший алгоритм для этого
Во-первых, если в списке ноль или один элемент, он уже отсортирован, поэтому возвращаем неизмененный список. Более длинные списки разделяются на два подсписка, каждый из которых содержит примерно половину элементов начального списка. Каждый подсписок сортируется рекурсивным вызовом сортирующей функции, и два получившихся отсортированных списка затем сливаются.
Строка 2421 ⟶ 2422 :
</syntaxhighlight></font>
Сложность <tt>msort</tt>
Вот пример того, как используется <tt>msort</tt>:
Строка 2537 ⟶ 2538 :
</syntaxhighlight></font>
Операция, имеющая отношение к фильтрации
<font size=3><syntaxhighlight lang=Scala>
Строка 2546 ⟶ 2547 :
</syntaxhighlight></font>
Чтобы проиллюстрировать, как используется <tt>forall</tt>, рассмотрим вопрос, является ли число простым. Число ''n''
<font size=3><syntaxhighlight lang=Scala>
Строка 2569 ⟶ 2570 :
<font size=3>'''Свертки и редукции списков'''</font>
Еще одна частая операция
<code>
Строка 2683 ⟶ 2684 :
Какова будет разница в асимптотической сложности между двумя версиями <tt>flatten</tt>?
На самом деле, <tt>flatten</tt> предопределен вместе со множеством других полезных функций в объекте <tt>List</tt> стандартной библиотеки Scala. Его можно вызвать из пользовательской программы при помощи <tt>List.flatten</tt>. Обратить внимание, что <tt>flatten</tt> не является методом класса <tt>List</tt>
Строка 2739 ⟶ 2740 :
Мы можем использовать функции высшего порядка для работы со списками, чтобы выразить многие вычисления, которые обычно выражаются при помощи вложенных циклов в императивных языках.
Для примера рассмотрим следующую задачу: дано положительное целое число ''n'', найти все пары положительных чисел ''i'' и ''j'', где <tt>1 ≤ j < i < n</tt>, таких что ''i + j''
{| class="wikitable"
Строка 2770 ⟶ 2771 :
|}
Естественный путь решения этой задачи состоит из двух частей. На первом шаге сгенерируем последовательность всех пар целых чисел ''(i, j)'', таких что <tt>1 ≤ j < i < n</tt>. На втором шаге отфильтруем из этой последовательности все пары ''(i, j)'', такие что ''i + j''
Посмотрим на первый шаг более детально. Естественный способ генерации последовательности пар состоит из трех подпунктов. Во-первых, сгенерируем все целые числа от <tt>1</tt> до ''n''. Во-вторых, для каждого целого числа ''i'' между <tt>1</tt> и ''n'' сгенерируем список пар от ''(i, 1)'' до ''(i, i
<font size=3><syntaxhighlight lang=Scala>
Строка 2790 ⟶ 2791 :
<font size=3>'''Сглаживающие преобразования'''</font>
Комбинация преобразования и затем конкатенации подсписков результатов преобразования
<font size=3><syntaxhighlight lang=Scala>
Строка 2801 ⟶ 2802 :
</syntaxhighlight></font>
При помощи <tt>flatMap</tt> выражение для пар, чья сумма
<font size=3><syntaxhighlight lang=Scala>
Строка 2812 ⟶ 2813 :
= For-Comprehensions =
== Задача N ферзей ==
== Очереди с For-Comprehensions ==
== Трансляция For-Comprehensions ==
== Цикл For ==
== Обобщение For ==
= Изменяемое состояние =
== Объекты с состоянием ==
== Императивные структуры контроля ==
== Расширенный пример: симуляция распределенных событий ==
== Заключение ==
= Вычисления с потоками =
В предыдущих главах рассматривались переменные, присваивание и объекты, сохраняющие состояние. Мы видели, как объекты реального мира, которые изменяются со временем, могут быть смоделированы изменением состояния переменных в вычислении. Таким образом временные изменения в реальном мире моделируются временными изменениями в выполнении программы. Конечно, такие временные изменения обычно сокращены или растянуты, но их относительный порядок остается неизменным. Такой подход представляется вполне естественным, но за него приходится платить: наша простая и выразительная модель
подстановок для функционального вычисления будет более не применима, если мы введем переменные
и присваивание.
Есть ли другой способ? Можем ли мы моделировать изменение состояния в реальном мире, используя лишь функции без побочных эффектов? Руководствуясь математикой, определенно можно дать ответ:
интервала:
Строка 2845 ⟶ 2855 :
</syntaxhighlight></font>
Заметьте, переменная <tt>i</tt>
Более функциональный способ заключается в представлении списка значений <tt>i</tt> явно, как <tt>range(start, end).</tt> Тогда функция может быть переписана так:
Строка 2854 ⟶ 2864 :
</syntaxhighlight></font>
Бесспорно, вторая программа короче и яснее! Однако функциональная программа также и менее эффективна, поскольку она конструирует список чисел из интервала, а затем еще один для простых чисел.
Еще хуже с точки зрения эффективности следующий пример.
Строка 2881 ⟶ 2891 :
(Эта функция, как и предыдущая, тоже определена в модуле <tt>Stream</tt>). Даже хотя <tt>Stream.range</tt> и <tt>List.range</tt> выглядят похоже, их поведение во время выполнения совершенно разное:
<tt>Stream.range</tt> немедленно возвращает объект <tt>Stream</tt>, первый элемент которого
Доступ к потокам
<font size=3><syntaxhighlight lang=Scala>
Строка 2899 ⟶ 2909 :
Различие с предыдущей списковой реализацей в том, что теперь нет необходимости конструировать и проверять на простоту числа после <tt>1013</tt>.
'''Склеивание потоков.''' Два метода из класса <tt>List</tt>, не поддерживаемые классом <tt>Stream</tt> это <tt>::</tt> и <tt>:::</tt>. Причина в том, что эти методы раскрываются с правых аргументов, а это означает, что эти аргументы должны быть вычислены до вызова метода. Например, в случае <tt>x :: xs</tt> на списках, хвост <tt>xs</tt> должен быть вычислен до вызова оператора <tt>::</tt> и конструирования нового списка. Это не работает для потоков, поскольку хвост потока не должен вычисляться, пока не потребуется операции <tt>tail</tt>. Объяснение, почему метод <tt>:::</tt> не адаптируется для потоков
Вместо <tt>x :: xs</tt> для создания потока с первым элементом <tt>x</tt> и (невычисленным) остатком <tt>xs</tt> используют <tt>Stream.cons(x, xs)</tt>. Вместо <tt>xs ::: ys</tt> используют операцию <tt>xs append ys</tt>.
= Итераторы =
== Методы итераторов ==
== Создание итераторов ==
== Использование итераторов ==
Строка 2912 ⟶ 2925 :
= Неявные(имплицитные) параметры и преобразования =
Неявные(имплицитные) параметры и преобразования
<font size=3><syntaxhighlight lang=Scala>
Строка 2920 ⟶ 2933 :
</syntaxhighlight></font>
А вот подкласс <tt>SemiGroup</tt>
<font size=3><syntaxhighlight lang=Scala>
Строка 2969 ⟶ 2982 :
</pre>
Если ключевое слово присутствует, то оно делает все параметры в списке неявными. Например, в следующей версии метода sum параметр m является неявным.
<font size=3><syntaxhighlight lang=Scala>
Строка 2977 ⟶ 2990 :
</syntaxhighlight></font>
Как можно заметить из примера, возможно комбинировать обычные и неявные параметры. Однако, для метода или конструктора может быть только один неявный параметр, и он должен быть последним.
Ключевое слово '''<tt>implicit</tt>''' может также быть использовано как модификатор в определениях и объявлениях. Примеры:
Строка 2993 ⟶ 3006 :
</syntaxhighlight></font>
Основная идея неявных параметров
Действительные аргументы, которые могут быть переданы в качестве неявного параметра,
Если существует несколько аргументов, подходящих по типу к неявному параметру, компилятор Scala выберет наиболее точный (специфичный), используя стандартные правила разрешения статической перегрузки. Допустим, вызывается
<font size=3><syntaxhighlight lang=Scala>
Строка 3003 ⟶ 3016 :
</syntaxhighlight></font>
в контексте, где доступны <tt>stringMonoid</tt> и <tt>intMonoid</tt>. Мы знаем, что формальный параметр метода <tt>sum</tt> должен быть типа <tt>Int</tt>. Единственное значение, подходящее к типу формального параметра <tt>Monoid[Int]</tt>, это <tt>intMonoid</tt>, поэтому этот объект и будет передан как неявный параметр.
Это обсуждение также показывает, что неявный параметр выводится после того, как выведен какой-нибудь типовой аргумент.
Строка 3010 ⟶ 3023 :
<font size=3>'''Неявные преобразования'''</font>
Пусть у вас есть выражение ''E'' типа ''T'', хотя ожидается тип ''S''. ''T'' не приводится к ''S'' ни одним из предопределенных преобразований. Тогда компилятор Scala попытается применить последнее средство: неявное преобразование ''I(E)''. Здесь, ''I''
Неявные преобразования также могут быть применены в селекторах членов. Получив ''E.x'', где ''x''
Вот пример неявного преобразования функции, которая преобразует целые числа в экземпляры класса <tt>scala.Ordered</tt>:
Строка 3028 ⟶ 3041 :
<font size=3>'''View Bounds'''</font>
View bounds (ограничения по видимости)
<font size=3><syntaxhighlight lang=Scala>
Строка 3088 ⟶ 3101 :
</syntaxhighlight></font>
Метод <tt>synchronized</tt> вычисляет свой аргумент <tt>e</tt> в режиме взаимного исключения
Поток может приостановиться на мониторе, ожидая сигнала. Поток, вызвавший метод <tt>wait</tt>, ждет до вызова другим потоком метода <tt>notify</tt> на том же объекте. Вызовы <tt>notify</tt> при отсутствии потоков, ожидающих сигнала, игнорируются.
Строка 3100 ⟶ 3113 :
</syntaxhighlight></font>
В качестве примера использования мониторов
<font size=3><syntaxhighlight lang=Scala>
Строка 3170 ⟶ 3183 :
== Futures ==
''Future'' (будущее)
<font size=3><syntaxhighlight lang=Scala>
Строка 3234 ⟶ 3247 :
== Семафоры ==
Классический механизм синхронизации процессов
<font size=3><syntaxhighlight lang=Scala>
Строка 3254 ⟶ 3267 :
== Читатели/Писатели ==
Более сложная форма синхронизации различает ''читающие потоки'', которые имеют доступ к общему ресурсу, не модифицируя его, и потоки, которые могут и читать, и изменять ресурс
* одновременно может быть несколько читателей,
* в любой момент может только быть один писатель,
Строка 3289 ⟶ 3302 :
== Асинхронные каналы ==
Основной способ межпроцессной связи
<font size=3><syntaxhighlight lang=Scala>
Строка 3298 ⟶ 3311 :
</syntaxhighlight></font>
Чтобы обеспечить вставку и удаление элементов в связанный список, каждая ссылка на список указывает на узел, который предшествует узлу—вершине списка. Пустые связанные списки начинаются с пустого узла, следующий узел которого
Класс канала использует связанный список, чтобы хранить данные, которые были посланы, но еще не были прочитаны. Потоки, которые хотят читать из пустого канала, регистрируются, инкрементируя поле <tt>nreaders</tt>, и ожидают извещения.
Строка 3405 ⟶ 3418 :
</syntaxhighlight></font>
Выражения, которые нужно выполнять (
* абстрактным типом <tt>T</tt>, который описывает результат вычисления задания,
* методом без параметров <tt>task</tt> типа <tt>T</tt>, который обозначает выражение, которое должно быть вычислено,
Строка 3426 ⟶ 3439 :
== Почтовые ящики ==
Почтовые ящики
<font size=3><syntaxhighlight lang=Scala>
Строка 3555 ⟶ 3568 :
</syntaxhighlight></font>
Единственные различия
== Акторы ==
В [[Scala в примерах#Программирование с акторами и сообщениями|Главе 3]] представлен набросок электронного аукциона. Этот сервис основывался на высокоуровневых процессах
Код ниже отличается от реализации в пакете <tt>scala.actors</tt>, чтобы из примера было ясно, как можно реализовать простую версию акторов. Это не является описанием того, как акторы в действительности определены и реализованы в стандартной библиотеке Scala (это можно найти в документации Scala API).
Строка 3573 ⟶ 3586 :
</syntaxhighlight></font>
[[Категория:Функциональное программирование]]
[[Категория:Объектно-ориентированное программирование]]
[[Категория:Scala]]
|