Scala в примерах: различия между версиями

Содержимое удалено Содержимое добавлено
→‎Списки: — начат перевод главы
м Перемещение содержания влево, форматирование заголовков
Строка 1:
''Здесь ведется перевод книги Martin'а Odersky [http://www.scala-lang.org/docu/files/ScalaByExample.pdf Scala by examples], которая описывает основные приемы программирования на языке [[w:Scala (язык программирования)|Scala<small><sup>↑</sup></small>]]. Присоединяйтесь!''
 
{{TOC right}}
 
= Введение =
Строка 20 ⟶ 22 :
В качестве первого примера рассмотрим [http://ru.wikipedia.org/wiki/Быстрая_сортировка быструю сортировку].
 
<div style="width: 450px"><font size=3><syntaxhighlight lang=Scala>
def sort(xs: Array[Int]) {
def swap(i: Int, j: Int) {
Строка 45 ⟶ 47 :
sort1(0, xs.length - 1)
}
</syntaxhighlight></font></div>
 
Код очень похож на Java или С программу. Мы используем те же операторы и похожие управляющие конструкции. Есть также немного синтаксических отличий. В частности,
Строка 57 ⟶ 59 :
Но можно писать программы, которые будут выглядеть совершенно иначе. Вот снова быстрая сортировка, на этот раз написанная в функциональном стиле.
 
<div style="width: 450px"><font size=3><syntaxhighlight lang=Scala>
def sort(xs: Array[Int]): Array[Int] = {
if (xs.length <= 1) xs
Строка 68 ⟶ 70 :
}
}
</syntaxhighlight></font></div>
 
Функциональная программа кратко и точно передает сущность алгоритма быстрой сортировки:
Строка 83 ⟶ 85 :
В частности, метод <tt>filter</tt>, который принимает в качестве аргумента функцию — ''предикатная функция''. Предикатная функция должна переводить элементы массива в булевские значения. Результат выполнения <tt>filter</tt> — массив, состоящий из тех элементов исходного массива, которые удовлетворяют предикату, то есть на которых предикатная функция возвращает true. Метод <tt>filter</tt> класса <tt>Array[t]</tt>, следовательно, имеет сигнатуру
 
<div style="width: 450px"><font size=3><syntaxhighlight lang=Scala>
def filter(p: T => Boolean): Array[T]
</syntaxhighlight></font></div>
 
Здесь <tt>T => Boolean</tt> — запись типа функции, принимающей аргумент типа <tt>T</tt> и возвращающей булево значение. Функции, подобные <tt>filter</tt>, то есть принимающие другую функцию как аргумент или возвращающие функцию как результат, называются функциями высшего порядка.
Строка 99 ⟶ 101 :
Для эффективности и лучшего обнаружения ошибок цикл '''<tt>while</tt>''' представлен в Scala как примитивная конструкция языка. Но, в принципе, его можно было бы вынести в библиотеку, оформив как функцию. Вот возможная реализация:
 
<div style="width: 450px">
<font size="3"><syntaxhighlight lang=Scala>
def While(p: => Boolean)(s: => Unit) {
Строка 105 ⟶ 108 :
}
}
</syntaxhighlight></font></div>
 
Функция <tt>While</tt> принимает первым параметром функцию проверки, которая не имеет аргументов, и возвращает булево значение. Второй параметр это командная функция, которая также не имеет аргументов и возвращает результат типа <tt>Unit</tt>. While вызывает командную функцию до тех пор, пока предикатная функция возвращает <tt>true</tt>.
Строка 111 ⟶ 114 :
Тип <tt>Unit</tt> в Scala, грубо говоря, соответствует <tt>void</tt> в Java; он используется всякий раз, когда функция не возвращает какого-либо интересного значения. На самом деле, поскольку Scala это язык, ориентированный на выражения, каждая функция возвращает некоторый результат. Если нет явно заданного выражения, то предполагается значение по умолчанию, <tt>()</tt> (произносится "unit"). Это значение имеет тип <tt>Unit</tt>. Функции, возвращающие <tt>Unit</tt>, также называются ''процедурами''. Вот более "ориентированная на выражения" формулировка функции swap из первой реализации быстрой сортировки, которая выражает это явно:
 
<div style="width: 450px"><font size="3">
<syntaxhighlight lang=Scala>
def swap(i: Int, j: Int) {
Строка 119 ⟶ 122 :
}
</syntaxhighlight>
</font></div>
 
Результат этой функции — просто ее последнее выражение, при этом ключевое слово <tt>'''return'''</tt> указывать не обязательно. Обратите внимание, что для функций, возвращающих явное значение, всегда необходимо cтавить '=' перед их телом или определяющим выражением.
Строка 877 ⟶ 880 :
Главы [[Scala в примерах#Выражения и простые функции|4]] и [[Scala в примерах#Функции первого класса|5]] покрывают элементы языка Scala для выражения выражений и типов, включая примитивные типы и функции. Ниже дан контекстно-свободный синтаксис этих языковых элементов в расширенной форме Бэкуса-Наура, где '<tt>|</tt>' обозначает альтернативы, <tt>[…]</tt> обозначает опцию (вхождение 1 или 0 раз), и <tt>{…}</tt> обозначает повтороение (0 или больше вхождений).
 
 
=== Символы ===
<font size=3>'''Символы'''</font>
 
Программы Scala — это последовательности символов Unicode. Мы различаем следующие множества символов:
Строка 886 ⟶ 890 :
* операторные символы, такие как ‘<tt>#</tt>’, ‘<tt>+</tt>’, ‘<tt>:</tt>’. По сути, это символы, которые не перечислены в множествах выше.
 
 
=== Лексемы ===
<font size=3>'''Лексемы'''</font>
 
<pre>
Строка 954 ⟶ 959 :
<tt>_&nbsp; &nbsp; :&nbsp; &nbsp; =&nbsp; &nbsp; =>&nbsp; &nbsp; <-&nbsp; &nbsp; <:&nbsp; &nbsp; <%&nbsp; &nbsp; >:&nbsp; &nbsp; #&nbsp; &nbsp; @</tt>
 
 
=== Типы ===
<font size=3>'''Типы'''</font>
 
<pre>
Строка 970 ⟶ 976 :
* типами функций, как <tt>(Int, Int) => Int</tt> или <tt>String => Int => String</tt>.
 
 
<font size=== 3>'''Выражения ==='''</font>
 
<pre>
Строка 994 ⟶ 1001 :
* анонимными функциями, как <tt>x => x + 1</tt> или <tt>(x: Int, y: Int) => x + y</tt>.
 
 
<font size=== 3>'''Определения ==='''</font>
 
<pre>
Строка 1039 ⟶ 1047 :
Это определяет <tt>Rational</tt> как класс, принимающий два аргумента конструктора <tt>n</tt> и <tt>d</tt>, содержащие числитель и знаменатель числа. Класс предоставляет поля, возвращающие эти части, и методы для арифметических операций с рациональными числами. Левый операнд операции — всегда рациональное число, для которого метод является членом (класса).
 
 
<font size=== 3>'''Приватные члены ==='''</font>
 
Реализация рациональных чисел определяет приватный метод <tt>gcd</tt>, который считает наибольший общий делитель двух целых чисел, и приватное поле <tt>g</tt>, содержащее <tt>gcd</tt> аргументов конструктора. Эти члены недоступны вне класса <tt>Rational</tt>. Они используются для реализации класса, чтобы сократить общие множители аргументов конструктора для приведения числителя и знаменателя к нормализованной форме.
 
 
<font size=== 3>'''Создание и доступ к объектам ==='''</font>
 
Для примера того, как могут быть использованы рациональные числа, приведем программу, выводящую на экран сумму всех чисел <tt>1/''i''</tt>, где <tt>''i''</tt> пробегает значения от <tt>1</tt> до <tt>10</tt>.
Строка 1059 ⟶ 1069 :
Оператор <tt>+</tt> принимает строку в качестве левого операнда и значение любого типа в качестве правого. Он возвращает результат преобразования своего правого операнда в строку и конкатенации левого операнда с этой строкой.
 
 
<font size=== 3>'''Наследование и переопределение ==='''</font>
 
У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип — псевдоним <tt>java.lang.Object</tt>). Например, класс <tt>Rational</tt> можно эквивалентно определить так:
Строка 1095 ⟶ 1106 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Методы без параметров ==='''</font>
 
В отличие от Java, методы в Scala не обязательно должны принимать список параметров. Пример тому — метод <tt>square</tt>, представленный ниже. Этот метод вызывается просто упоминанием своего имени.
Строка 1110 ⟶ 1122 :
То есть, доступ к методам без параметров такой же, как к полям значений, таких как <tt>numer</tt>. Разница между значениями и методами без параметров в их определении. Правая часть значения вычисляется, когда создается объект, и значение после этого не изменяется. Правая часть метода без параметров, напротив, вычисляется каждый раз, когда метод вызывается. Общий вид доступа к полям и методам без параметров дает повышенную гибкость для разработчика класса. Часто поле в одной версии класса становится вычисляемым значением в следующей версии. Общий вид доступа дает гарантию того, что клиенты не нужно будет переписывать из-за этого изменения.
 
 
<font size=== 3>'''Абстрактные классы ==='''</font>
 
Реализуем класс для множеств целых чисел с двумя операциями, <tt>incl</tt> и <tt>contains</tt>. <tt>(s incl x)</tt> должно возвращать новое множество, содержащее элемент <tt>x</tt> и все элементы множества <tt>s</tt>. <tt>(s contains x)</tt> должно возвращать <tt>'''true'''</tt>, если множество <tt>s</tt> содержит элемент <tt>x</tt>, и <tt>'''false'''</tt> в противном случае. Интерфейс множеств задан так:
Строка 1123 ⟶ 1136 :
<tt>IntSet</tt> помечен как ''абстрактный класс'' (abstract class). У этого есть два следствия. Во-первых, абстрактные классы могут ''откладывать'' (defer) члены, которые объявлены, но у которых нет реализации. В нашем случае оба метода <tt>incl</tt> и <tt>contains</tt> таковы. Во-вторых, из-за того, что у абстрактного класса могут быть нереализованные члены, объект такого класса не может быть создан при помощи <tt>'''new'''</tt>. Абстрактные классы могут быть использованы как базовые классы для других классов, реализующих из отложенные методы.
 
 
=== Трейты ===
<font size=3>'''Трейты'''</font>
 
Вместо <tt>'''abstract class'''</tt> в Scala часто используют ключевое слово <tt>'''trait'''</tt>. Трейты это абстрактные классы, которые задуманы, чтобы добавлять их к другим классам, потому что трейты добавляют к классам некоторые методы или поля. Например, трейт <tt>Bordered</tt> может быть использован, чтобы добавить границу к любому графическому компоненту. Другой сценарий использования происходит, когда трейт вбирает в себя сигнатуры некоторой функциональности, обеспечиваемой различными классами (похоже на интерфейсы в Java).
Строка 1136 ⟶ 1150 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Реализация абстрактных классов ==='''</font>
 
Допустим, мы хотим реализовать множества в виде двоичных деревьев. Есть две разные формы деревьев: дерево для пустого множества и дерево, состоящее из целого числа и двух поддеревьев. Вот их реализация:
Строка 1174 ⟶ 1189 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Динамическое связывание ==='''</font>
 
Объектно-ориентированные языки (включая Scala) используют ''динамическое назначение'' (dynamic dispatch) для вызовов методов. То есть, код, который запускается при вызове метода, зависит от типа объекта, содержащего метод, во время выполнения. Например, рассмотрим выражение <tt>s contains 7</tt>, в котором <tt>s</tt> это значение заявленного типа <tt>s: IntSet</tt>. Какой код для <tt>contains</tt> будет выполнен, зависит от типа значения во время выполнения. Если это значение <tt>EmptySet</tt>, это будет реализация <tt>contains</tt> в классе <tt>EmptySet</tt> (аналогично для значений <tt>NonEmptySet</tt>). Такое поведение прямо следует из нашей подстановочной модели вычислений. Например,
Строка 1208 ⟶ 1224 :
Динамическое назначение методов аналогично вызовам функций высшего порядка. В обоих этих случаях только во время выполнения известно, какой код будет выполняться. Это сходство не является поверхностным. Действительно, Scala представляет каждую фукцию как объект (см. [[Scala в примерах#Функции|Параграф 8.6]]).
 
 
=== Объекты ===
<font size=3>'''Объекты'''</font>
 
В предыдущей реализации множеств целых чисел пустые множества были выражены при помощи <tt>'''new''' EmptySet</tt>, так что каждый раз, когда требовалось значение пустого множества, создавался новый объект. Мы могли бы избежать создания ненужных объектов, определив значение <tt>empty</tt> однажды и используя это значение вместо каждого <tt>'''new''' EmptySet</tt>. Например,
Строка 1229 ⟶ 1246 :
Определения объектов могут появляться в любом месте программ на Scala, включая высший уровень. Поскольку не существует фиксированного порядка выполнения сущностей высшего уровня в Scala, может возникнуть вопрос, когда в точности создается и инициализируется объект, определенный при помощи определения объекта. Объект впервые создается, когда впервые происходит доступ к одному из его членов. Такая стратегия называется ''ленивым вычислением''.
 
 
<font size=== 3>'''Стандартные классы ==='''</font>
 
Scala — чисто объектно-ориентированный язык. Это значит, что каждое значение в Scala может быть рассмотрено как объект. На самом деле даже примитивные типы, как <tt>int</tt> или <tt>boolean</tt>, не рассматриваются специально. Они определены как псевдонимы типов классов Scala в модуле <tt>Predef</tt>:
Строка 1367 ⟶ 1385 :
Первый метод должен возвращать <tt>'''true'''</tt>, если число положительное. Второй метод должен домножать число на <tt>-1</tt>. Не используйте в своей реализации стандартные числовые классы Scala. (Подсказка: есть два возможных способа реализовать <tt>Integer</tt>. Можно либо использовать существующую реализацию <tt>Nat</tt>, представляя целые числа как натуральные со знаком, либо обобщить данную реализацию <tt>Nat</tt> до <tt>Integer</tt>, используя три подкласса: <tt>Zero</tt> для <tt>0</tt>, <tt>Succ</tt> для положительных чисел и <tt>Pred</tt> для отицательных.)
 
=== Элементы языка, представленные в этой главе ===
 
<font size=== 3>'''Элементы языка, представленные в этой главе ==='''</font>
==== Типы ====
 
<font size=2>'''Типы'''</font>
 
<pre>
Строка 1377 ⟶ 1396 :
Типы теперь могут быть произвольными идентификаторами, представляющими классы.
 
<font size==== 2>'''Выражения ===='''</font>
 
<pre>
Строка 1385 ⟶ 1404 :
Выражение теперь может быть созданием объекта, выбором члена <tt>m</tt> из выражения с объектным значением <tt>E</tt> (<tt>E.m</tt>) или зарезервированным именем <tt>'''this'''</tt>.
 
<font size==== 2>'''Определения и объявления ===='''</font>
 
<pre>
Строка 1602 ⟶ 1621 :
Переменные образца всегда начинаются с буквы в нижнем регистре, чтобы их можно было отличить от идентификаторов констант, которые начинаются с буквы в верхнем регистре. Каждое имя переменной может встречаться в образце лишь однажды. Например, выражение <tt>Sum(x, x)</tt> недопустимо в качестве образца, потому что переменная <tt>x</tt> встречается в нем дважды.
 
 
<font size=== 3>'''Смысл cопоставления с образцом ==='''</font>
 
Выражение cопоставления с образцом
Строка 1649 ⟶ 1669 :
</pre>
 
 
<font size=== 3>'''Сопоставление с образцом и методы ==='''</font>
 
В предыдущем примере мы использовали сопоставление с образцом в функции, которая определена вне иерархии классов, на которой происходит сопоставление. Конечно, можно также определить функцию сопоставления внутри самой иерархии. Например, можно определить <tt>eval</tt> как метод базового класса <tt>Expr</tt>, не убирая из его реализации сопоставление с образцом:
Строка 1681 ⟶ 1702 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Анонимные функции сопоставления с образцом ==='''</font>
 
До этого case-выражения всегда появлялись вместе с оператором <tt>match</tt>. Но можно также использовать case-выражения сами по себе. Блок case-выражений, такой как
Строка 2143 ⟶ 2165 :
== Использование списков ==
 
 
=== Тип List ===
<font size=3>'''Тип List'''</font>
 
Как и массивы, списки ''гомогенны''. Это значит, все элементы списка — одного типа. Тип списка с элементами типа <tt>T</tt> записывается как <tt>List[T]</tt> (похоже на <tt>T[]</tt> в Java).
Строка 2154 ⟶ 2177 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Конструкторы списков ==='''</font>
 
Все списки строятся при помощи двух более фундаментальных конструкторов, <tt>Nil</tt> и <tt>::</tt> (произвносится "cons"). <tt>Nil</tt> выражает пустой список. Инфиксный оператор <tt>::</tt> выражает продление списка. То есть, <tt>x :: xs</tt> выражает список с первым элементом <tt>x</tt>, за которым идут элементы списка <tt>xs</tt>. Значения списков, приведенные выже, могут быть определены так (на самом деле, их предыдущие определения это синтаксический сахар для определений ниже):
Строка 2173 ⟶ 2197 :
</syntaxhighlight></font>
 
 
<font size=== 3>'''Базовые операции со списками ==='''</font>
 
Все операции со списками можно выразить при помощи следующих трех конструкций:
Строка 2202 ⟶ 2227 :
'''Упражнение 9.1.1''' Реализуйте недостающую функцию <tt>insert</tt>.
 
 
<font size=== 3>'''Образцы списков ==='''</font>
 
На самом деле, оператор <tt>::</tt> определен как case-класс в стандартной библиотеке Scala. Возможно раскладывать списки при помощи сопоставления с образцом, используя образцы, составленные из конструкторов <tt>Nil</tt> и <tt>::</tt>. Например, <tt>isort</tt> можно написать так:
Строка 2375 ⟶ 2401 :
Все это работает, но выглядит не очень здорово. Проблема в том, что реализации моноида должны быть переданы в код, который их использует. Порой мы можем захотеть, чтобы система могла вычислить нужные аргументы автоматически, аналогично тому, как это делается при выводе типов аргументов. Это то, чего помогают добиться неявные параметры.
 
 
<font size=== 3>'''Неявные параметры: основы ==='''</font>
 
В Scala 2 появилось новое ключевое слово '''<tt>implicit</tt>''', оно может быть использовано в начале списка параметров. Синтаксис:
Строка 2422 ⟶ 2449 :
Это обсуждение также показывает, что неявный параметр выводится после того, как выведен какой-нибудь типовой аргумент.
 
 
<font size=== 3>'''Неявные преобразования ==='''</font>
 
Пусть у вас есть выражение ''E'' типа ''T'', хотя ожидается тип ''S''. ''T'' не приводится к ''S'' ни одним из предопределенных преобразований. Тогда компилятор Scala попытается применить последнее средство: неявное преобразование ''I(E)''. Здесь, ''I'' — это идентификатор, выражающий неявное определение или параметр, доступный без префикса в точке преобразования, применимый к аргументам типа ''T'' и чей результирующий тип совместим с типом ''S''.
Строка 2439 ⟶ 2467 :
</syntaxhighlight></font>
 
 
=== View Bounds ===
<font size=3>'''View Bounds'''</font>
 
View bounds (ограничения по видимости) — это удобный синтаксический сахар для неявных параметров. Рассмотрим обобщенный метод сортировки: