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

718 байт добавлено ,  8 лет назад
м
Перемещение содержания влево, форматирование заголовков
(→‎Списки: — начат перевод главы)
м (Перемещение содержания влево, форматирование заголовков)
''Здесь ведется перевод книги Martin'а Odersky [http://www.scala-lang.org/docu/files/ScalaByExample.pdf Scala by examples], которая описывает основные приемы программирования на языке [[w:Scala (язык программирования)|Scala<small><sup>↑</sup></small>]]. Присоединяйтесь!''
 
{{TOC right}}
 
= Введение =
В качестве первого примера рассмотрим [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) {
sort1(0, xs.length - 1)
}
</syntaxhighlight></font></div>
 
Код очень похож на Java или С программу. Мы используем те же операторы и похожие управляющие конструкции. Есть также немного синтаксических отличий. В частности,
Но можно писать программы, которые будут выглядеть совершенно иначе. Вот снова быстрая сортировка, на этот раз написанная в функциональном стиле.
 
<div style="width: 450px"><font size=3><syntaxhighlight lang=Scala>
def sort(xs: Array[Int]): Array[Int] = {
if (xs.length <= 1) xs
}
}
</syntaxhighlight></font></div>
 
Функциональная программа кратко и точно передает сущность алгоритма быстрой сортировки:
В частности, метод <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>, то есть принимающие другую функцию как аргумент или возвращающие функцию как результат, называются функциями высшего порядка.
Для эффективности и лучшего обнаружения ошибок цикл '''<tt>while</tt>''' представлен в Scala как примитивная конструкция языка. Но, в принципе, его можно было бы вынести в библиотеку, оформив как функцию. Вот возможная реализация:
 
<div style="width: 450px">
<font size="3"><syntaxhighlight lang=Scala>
def While(p: => Boolean)(s: => Unit) {
}
}
</syntaxhighlight></font></div>
 
Функция <tt>While</tt> принимает первым параметром функцию проверки, которая не имеет аргументов, и возвращает булево значение. Второй параметр это командная функция, которая также не имеет аргументов и возвращает результат типа <tt>Unit</tt>. While вызывает командную функцию до тех пор, пока предикатная функция возвращает <tt>true</tt>.
Тип <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) {
}
</syntaxhighlight>
</font></div>
 
Результат этой функции — просто ее последнее выражение, при этом ключевое слово <tt>'''return'''</tt> указывать не обязательно. Обратите внимание, что для функций, возвращающих явное значение, всегда необходимо cтавить '=' перед их телом или определяющим выражением.
Главы [[Scala в примерах#Выражения и простые функции|4]] и [[Scala в примерах#Функции первого класса|5]] покрывают элементы языка Scala для выражения выражений и типов, включая примитивные типы и функции. Ниже дан контекстно-свободный синтаксис этих языковых элементов в расширенной форме Бэкуса-Наура, где '<tt>|</tt>' обозначает альтернативы, <tt>[…]</tt> обозначает опцию (вхождение 1 или 0 раз), и <tt>{…}</tt> обозначает повтороение (0 или больше вхождений).
 
 
=== Символы ===
<font size=3>'''Символы'''</font>
 
Программы Scala — это последовательности символов Unicode. Мы различаем следующие множества символов:
* операторные символы, такие как ‘<tt>#</tt>’, ‘<tt>+</tt>’, ‘<tt>:</tt>’. По сути, это символы, которые не перечислены в множествах выше.
 
 
=== Лексемы ===
<font size=3>'''Лексемы'''</font>
 
<pre>
<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>
* типами функций, как <tt>(Int, Int) => Int</tt> или <tt>String => Int => String</tt>.
 
 
<font size=== 3>'''Выражения ==='''</font>
 
<pre>
* анонимными функциями, как <tt>x => x + 1</tt> или <tt>(x: Int, y: Int) => x + y</tt>.
 
 
<font size=== 3>'''Определения ==='''</font>
 
<pre>
Это определяет <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>.
Оператор <tt>+</tt> принимает строку в качестве левого операнда и значение любого типа в качестве правого. Он возвращает результат преобразования своего правого операнда в строку и конкатенации левого операнда с этой строкой.
 
 
<font size=== 3>'''Наследование и переопределение ==='''</font>
 
У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип — псевдоним <tt>java.lang.Object</tt>). Например, класс <tt>Rational</tt> можно эквивалентно определить так:
</syntaxhighlight></font>
 
 
<font size=== 3>'''Методы без параметров ==='''</font>
 
В отличие от Java, методы в Scala не обязательно должны принимать список параметров. Пример тому — метод <tt>square</tt>, представленный ниже. Этот метод вызывается просто упоминанием своего имени.
То есть, доступ к методам без параметров такой же, как к полям значений, таких как <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> в противном случае. Интерфейс множеств задан так:
<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).
</syntaxhighlight></font>
 
 
<font size=== 3>'''Реализация абстрактных классов ==='''</font>
 
Допустим, мы хотим реализовать множества в виде двоичных деревьев. Есть две разные формы деревьев: дерево для пустого множества и дерево, состоящее из целого числа и двух поддеревьев. Вот их реализация:
</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>). Такое поведение прямо следует из нашей подстановочной модели вычислений. Например,
Динамическое назначение методов аналогично вызовам функций высшего порядка. В обоих этих случаях только во время выполнения известно, какой код будет выполняться. Это сходство не является поверхностным. Действительно, Scala представляет каждую фукцию как объект (см. [[Scala в примерах#Функции|Параграф 8.6]]).
 
 
=== Объекты ===
<font size=3>'''Объекты'''</font>
 
В предыдущей реализации множеств целых чисел пустые множества были выражены при помощи <tt>'''new''' EmptySet</tt>, так что каждый раз, когда требовалось значение пустого множества, создавался новый объект. Мы могли бы избежать создания ненужных объектов, определив значение <tt>empty</tt> однажды и используя это значение вместо каждого <tt>'''new''' EmptySet</tt>. Например,
Определения объектов могут появляться в любом месте программ на Scala, включая высший уровень. Поскольку не существует фиксированного порядка выполнения сущностей высшего уровня в Scala, может возникнуть вопрос, когда в точности создается и инициализируется объект, определенный при помощи определения объекта. Объект впервые создается, когда впервые происходит доступ к одному из его членов. Такая стратегия называется ''ленивым вычислением''.
 
 
<font size=== 3>'''Стандартные классы ==='''</font>
 
Scala — чисто объектно-ориентированный язык. Это значит, что каждое значение в Scala может быть рассмотрено как объект. На самом деле даже примитивные типы, как <tt>int</tt> или <tt>boolean</tt>, не рассматриваются специально. Они определены как псевдонимы типов классов Scala в модуле <tt>Predef</tt>:
Первый метод должен возвращать <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>
Типы теперь могут быть произвольными идентификаторами, представляющими классы.
 
<font size==== 2>'''Выражения ===='''</font>
 
<pre>
Выражение теперь может быть созданием объекта, выбором члена <tt>m</tt> из выражения с объектным значением <tt>E</tt> (<tt>E.m</tt>) или зарезервированным именем <tt>'''this'''</tt>.
 
<font size==== 2>'''Определения и объявления ===='''</font>
 
<pre>
Переменные образца всегда начинаются с буквы в нижнем регистре, чтобы их можно было отличить от идентификаторов констант, которые начинаются с буквы в верхнем регистре. Каждое имя переменной может встречаться в образце лишь однажды. Например, выражение <tt>Sum(x, x)</tt> недопустимо в качестве образца, потому что переменная <tt>x</tt> встречается в нем дважды.
 
 
<font size=== 3>'''Смысл cопоставления с образцом ==='''</font>
 
Выражение cопоставления с образцом
</pre>
 
 
<font size=== 3>'''Сопоставление с образцом и методы ==='''</font>
 
В предыдущем примере мы использовали сопоставление с образцом в функции, которая определена вне иерархии классов, на которой происходит сопоставление. Конечно, можно также определить функцию сопоставления внутри самой иерархии. Например, можно определить <tt>eval</tt> как метод базового класса <tt>Expr</tt>, не убирая из его реализации сопоставление с образцом:
</syntaxhighlight></font>
 
 
<font size=== 3>'''Анонимные функции сопоставления с образцом ==='''</font>
 
До этого case-выражения всегда появлялись вместе с оператором <tt>match</tt>. Но можно также использовать case-выражения сами по себе. Блок case-выражений, такой как
== Использование списков ==
 
 
=== Тип List ===
<font size=3>'''Тип List'''</font>
 
Как и массивы, списки ''гомогенны''. Это значит, все элементы списка — одного типа. Тип списка с элементами типа <tt>T</tt> записывается как <tt>List[T]</tt> (похоже на <tt>T[]</tt> в Java).
</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>. Значения списков, приведенные выже, могут быть определены так (на самом деле, их предыдущие определения это синтаксический сахар для определений ниже):
</syntaxhighlight></font>
 
 
<font size=== 3>'''Базовые операции со списками ==='''</font>
 
Все операции со списками можно выразить при помощи следующих трех конструкций:
'''Упражнение 9.1.1''' Реализуйте недостающую функцию <tt>insert</tt>.
 
 
<font size=== 3>'''Образцы списков ==='''</font>
 
На самом деле, оператор <tt>::</tt> определен как case-класс в стандартной библиотеке Scala. Возможно раскладывать списки при помощи сопоставления с образцом, используя образцы, составленные из конструкторов <tt>Nil</tt> и <tt>::</tt>. Например, <tt>isort</tt> можно написать так:
Все это работает, но выглядит не очень здорово. Проблема в том, что реализации моноида должны быть переданы в код, который их использует. Порой мы можем захотеть, чтобы система могла вычислить нужные аргументы автоматически, аналогично тому, как это делается при выводе типов аргументов. Это то, чего помогают добиться неявные параметры.
 
 
<font size=== 3>'''Неявные параметры: основы ==='''</font>
 
В Scala 2 появилось новое ключевое слово '''<tt>implicit</tt>''', оно может быть использовано в начале списка параметров. Синтаксис:
Это обсуждение также показывает, что неявный параметр выводится после того, как выведен какой-нибудь типовой аргумент.
 
 
<font size=== 3>'''Неявные преобразования ==='''</font>
 
Пусть у вас есть выражение ''E'' типа ''T'', хотя ожидается тип ''S''. ''T'' не приводится к ''S'' ни одним из предопределенных преобразований. Тогда компилятор Scala попытается применить последнее средство: неявное преобразование ''I(E)''. Здесь, ''I'' — это идентификатор, выражающий неявное определение или параметр, доступный без префикса в точке преобразования, применимый к аргументам типа ''T'' и чей результирующий тип совместим с типом ''S''.
</syntaxhighlight></font>
 
 
=== View Bounds ===
<font size=3>'''View Bounds'''</font>
 
View bounds (ограничения по видимости) — это удобный синтаксический сахар для неявных параметров. Рассмотрим обобщенный метод сортировки:
83

правки