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

Содержимое удалено Содержимое добавлено
Добавлен перевод главы 6.
мНет описания правки
Строка 1039:
Это определяет <tt>Rational</tt> как класс, принимающий два аргумента конструктора <tt>n</tt> и <tt>d</tt>, содержащие числитель и знаменатель числа. Класс предоставляет поля, возвращающие эти части, и методы для арифметических операций с рациональными числами. Левый операнд операции — всегда рациональное число, для которого метод является членом (класса).
 
=== Приватные члены ===
'''Приватные члены.''' Реализация рациональных чисел определяет приватный метод <tt>gcd</tt>, который считает наибольший общий делитель двух целых чисел, и приватное поле <tt>g</tt>, содержащее <tt>gcd</tt> аргументов конструктора. Эти члены недоступны вне класса <tt>Rational</tt>. Они используются для реализации класса, чтобы сократить общие множители аргументов конструктора для приведения числителя и знаменателя к нормализованной форме.
 
'''Приватные члены.''' Реализация рациональных чисел определяет приватный метод <tt>gcd</tt>, который считает наибольший общий делитель двух целых чисел, и приватное поле <tt>g</tt>, содержащее <tt>gcd</tt> аргументов конструктора. Эти члены недоступны вне класса <tt>Rational</tt>. Они используются для реализации класса, чтобы сократить общие множители аргументов конструктора для приведения числителя и знаменателя к нормализованной форме.
'''Создание и доступ к объектам.''' Для примера того, как могут быть использованы рациональные числа, приведем программу, выводящую на экран сумму всех чисел <tt>1/''i''</tt>, где <tt>''i''</tt> пробегает значения от <tt>1</tt> до <tt>10</tt>.
 
=== Создание и доступ к объектам ===
 
'''Создание и доступ к объектам.''' Для примера того, как могут быть использованы рациональные числа, приведем программу, выводящую на экран сумму всех чисел <tt>1/''i''</tt>, где <tt>''i''</tt> пробегает значения от <tt>1</tt> до <tt>10</tt>.
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1055 ⟶ 1059 :
Оператор <tt>+</tt> принимает строку в качестве левого операнда и значение любого типа в качестве правого. Он возвращает результат преобразования своего правого операнда в строку и конкатенации левого операнда с этой строкой.
 
=== Наследование и переопределение ===
'''Наследование и переопределение.''' У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип — псевдоним <tt>java.lang.Object</tt>). Например, класс <tt>Rational</tt> можно эквивалентно определить так:
 
'''Наследование и переопределение.''' У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип — псевдоним <tt>java.lang.Object</tt>). Например, класс <tt>Rational</tt> можно эквивалентно определить так:
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1089 ⟶ 1095 :
</syntaxhighlight></font>
 
'''=== Методы без параметров.''' ===

В отличие от Java, методы в Scala не обязательно должны принимать список параметров. Пример тому — метод <tt>square</tt>, представленный ниже. Этот метод вызывается просто упоминанием своего имени.
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1102 ⟶ 1110 :
То есть, доступ к методам без параметров такой же, как к полям значений, таких как <tt>numer</tt>. Разница между значениями и методами без параметров в их определении. Правая часть значения вычисляется, когда создается объект, и значение после этого не изменяется. Правая часть метода без параметров, напротив, вычисляется каждый раз, когда метод вызывается. Общий вид доступа к полям и методам без параметров дает повышенную гибкость для разработчика класса. Часто поле в одной версии класса становится вычисляемым значением в следующей версии. Общий вид доступа дает гарантию того, что клиенты не нужно будет переписывать из-за этого изменения.
 
=== Абстрактные классы ===
'''Абстрактные классы.''' Реализуем класс для множеств целых чисел с двумя операциями, <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>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> в противном случае. Интерфейс множеств задан так:
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1113 ⟶ 1123 :
<tt>IntSet</tt> помечен как ''абстрактный класс'' (abstract class). У этого есть два следствия. Во-первых, абстрактные классы могут ''откладывать'' (defer) члены, которые объявлены, но у которых нет реализации. В нашем случае оба метода <tt>incl</tt> и <tt>contains</tt> таковы. Во-вторых, из-за того, что у абстрактного класса могут быть нереализованные члены, объект такого класса не может быть создан при помощи <tt>'''new'''</tt>. Абстрактные классы могут быть использованы как базовые классы для других классов, реализующих из отложенные методы.
 
=== Трейты ===
'''Трейты.''' Вместо <tt>'''abstract class'''</tt> в Scala часто используют ключевое слово <tt>'''trait'''</tt>. Трейты это абстрактные классы, которые задуманы, чтобы добавлять их к другим классам, потому что трейты добавляют к классам некоторые методы или поля. Например, трейт <tt>Bordered</tt> может быть использован, чтобы добавить границу к любому графическому компоненту. Другой сценарий использования происходит, когда трейт вбирает в себя сигнатуры некоторой функциональности, обеспечиваемой различными классами (похоже на интерфейсы в Java).
 
'''Трейты.''' Вместо <tt>'''abstract class'''</tt> в Scala часто используют ключевое слово <tt>'''trait'''</tt>. Трейты это абстрактные классы, которые задуманы, чтобы добавлять их к другим классам, потому что трейты добавляют к классам некоторые методы или поля. Например, трейт <tt>Bordered</tt> может быть использован, чтобы добавить границу к любому графическому компоненту. Другой сценарий использования происходит, когда трейт вбирает в себя сигнатуры некоторой функциональности, обеспечиваемой различными классами (похоже на интерфейсы в Java).
 
Поскольку <tt>IntSet</tt> попадает в эту категорию, можно определить его как трейт:
Строка 1124 ⟶ 1136 :
</syntaxhighlight></font>
 
'''=== Реализация абстрактных классов.''' ===

Допустим, мы хотим реализовать множества в виде двоичных деревьев. Есть две разные формы деревьев: дерево для пустого множества и дерево, состоящее из целого числа и двух поддеревьев. Вот их реализация:
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1160 ⟶ 1174 :
</syntaxhighlight></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) используют ''динамическое назначение'' (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>). Такое поведение прямо следует из нашей подстановочной модели вычислений. Например,
 
<pre>
Строка 1192 ⟶ 1208 :
Динамическое назначение методов аналогично вызовам функций высшего порядка. В обоих этих случаях только во время выполнения известно, какой код будет выполняться. Это сходство не является поверхностным. Действительно, Scala представляет каждую фукцию как объект (см. [[Scala в примерах#Функции|Параграф 8.6]]).
 
=== Объекты ===
'''Объекты.''' В предыдущей реализации множеств целых чисел пустые множества были выражены при помощи <tt>'''new''' EmptySet</tt>, так что каждый раз, когда требовалось значение пустого множества, создавался новый объект. Мы могли бы избежать создания ненужных объектов, определив значение <tt>empty</tt> однажды и используя это значение вместо каждого <tt>'''new''' EmptySet</tt>. Например,
 
'''Объекты.''' В предыдущей реализации множеств целых чисел пустые множества были выражены при помощи <tt>'''new''' EmptySet</tt>, так что каждый раз, когда требовалось значение пустого множества, создавался новый объект. Мы могли бы избежать создания ненужных объектов, определив значение <tt>empty</tt> однажды и используя это значение вместо каждого <tt>'''new''' EmptySet</tt>. Например,
 
<font size=3><syntaxhighlight lang=Scala>
Строка 1211 ⟶ 1229 :
Определения объектов могут появляться в любом месте программ на Scala, включая высший уровень. Поскольку не существует фиксированного порядка выполнения сущностей высшего уровня в Scala, может возникнуть вопрос, когда в точности создается и инициализируется объект, определенный при помощи определения объекта. Объект впервые создается, когда впервые происходит доступ к одному из его членов. Такая стратегия называется ''ленивым вычислением''.
 
=== Стандартные классы ===
'''Стандартные классы.''' Scala — чисто объектно-ориентированный язык. Это значит, что каждое значение в Scala может быть рассмотрено как объект. На самом деле даже примитивные типы, как <tt>int</tt> или <tt>boolean</tt>, не рассматриваются специально. Они определены как псевдонимы типов классов Scala в модуле <tt>Predef</tt>:
 
'''Стандартные классы.''' Scala — чисто объектно-ориентированный язык. Это значит, что каждое значение в Scala может быть рассмотрено как объект. На самом деле даже примитивные типы, как <tt>int</tt> или <tt>boolean</tt>, не рассматриваются специально. Они определены как псевдонимы типов классов Scala в модуле <tt>Predef</tt>:
 
<font size=3><syntaxhighlight lang=Scala>