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

28 644 байта добавлено ,  8 лет назад
Добавлен перевод главы 6.
(Добавлен перевод главы 6.)
 
Главы [[Scala в примерах#Первый пример|2]] и [[Scala в примерах#Программирование с акторами и сообщениями|3]] обращают внимание на некоторые возможности, из-за которых Scala особенно интересен. Последующие главы представляют конструкты языка более подробно, начиная с простых выражений и функций, через объекты и классы, списки и потоки, изменяемые состояния, сопоставление с образцом, к более сложным примерам, иллюстрирующим интересные приемы программирования. Это неформальное изложение должно быть дополнено документом [http://www.scala-lang.org/docu/files/ScalaReference.pdf Scala Language Reference Manual], который специфицирует Scala более точно и детально.
 
 
'''Замечание.''' Мы в огромном долгу у чудесной книги Абельсона и Сассмана, "Структура и интерпретация компьютерных программ". Многие примеры и упражнения оттуда также приведены здесь. Конечно, рабочий язык в каждом случае был изменен с Scheme на Scala. Кроме того, в примерах использованы объектно-ориентированные конструкты Scala, где это уместно.
== Использованные элементы языка ==
 
Главы [[Scala в примерах#Выражения и простые функции|4]] и [[Scala в примерах#Функции первого класса|5]] покрывают элементы языка Scala для выражения выражений и типов, включая примитивные типы и функции. Ниже дан контекстно-свободный синтаксис этих языковых элементов в расширенной форме Бэкуса-Наура, где '<tt>|</tt>' обозначает альтернативы, <tt>[...]</tt> обозначает опцию (вхождение 1 или 0 раз), и <tt>{...}</tt> обозначает повтороение (0 или больше вхождений).
 
=== Символы ===
 
<pre>
Выражение Выр = ИнфиксноеВыражениеИнфиксВыр | ФункцияФункцВыр | if '(' ВыражениеВыр ')' ВыражениеВыр else ВыражениеВыр
ИнфиксВыр = ПрефиксВыр | ИнфиксВыр Оператор ИнфиксВыр
ИнфиксноеВыражение = ПрефиксноеВыражение | ИнфиксноеВыражение Оператор ИнфиксноеВыражение
Оператор = идентификатор
ПрефиксноеВыражениеПрефиксВыр = ['+' | '-' | '!' | '~'] ПростоеВыражение
ПростоеВыражени ПростоеВыр = идентификатор | литерал | ПростоеВыражениеПростоеВыр '.' идентификатор | Блок
Функция = (Связывания | Id) '=>' ВыражениеВыр
Связывания = '(' Связывание {',' Связывание } ')'
Связывание = идентификатор [':' Тип]
Блок = '{' {Определение ';'} ВыражениеВыр '}'
</pre>
 
 
<pre>
Опр = ОпрФункции | ОпрЗначения
Определение = ОпределениеФункции | ОпределениеЗначения
ОпределениеФункцииОпрФункции = 'def' идентификатор {'(' [Параметры ] ')'} [':' Тип] '=' ВыражениеВыр
ОпределениеЗначенияОпрЗначения = 'val' идентификатор [':' Тип] '=' ВыражениеВыр
Параметры = Параметр {',' Параметр}
Параметр = идентификатор ':' ['=>'] Тип
</pre>
 
 
= Классы и объекты =
 
В Scala нет встроенного типа для рациональных чисел, но его легко определить при помощи класса. Вот возможная реализация:
 
<font size=3><syntaxhighlight lang=Scala>
class Rational(n: Int, d: Int) {
private def gcd(x: Int, y: Int): Int = {
if (x == 0) y
else if (x < 0) gcd(-x, y)
else if (y < 0) -gcd(x, -y)
else gcd(y % x, x)
}
private val g = gcd(n, d)
val numer: Int = n/g val
denom: Int = d/g
def +(that: Rational) =
new Rational(numer * that.denom + that.numer * denom,
denom * that.denom)
def -(that: Rational) =
new Rational(numer * that.denom - that.numer * denom,
denom * that.denom)
def *(that: Rational) =
new Rational(numer * that.numer, denom * that.denom)
def /(that: Rational) =
new Rational(numer * that.denom, denom * that.numer)
}
</syntaxhighlight></font>
 
Это определяет <tt>Rational</tt> как класс, принимающий два аргумента конструктора <tt>n</tt> и <tt>d</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>.
 
<font size=3><syntaxhighlight lang=Scala>
var i = 1
var x = new Rational(0, 1)
while (i <= 10) {
x += new Rational(1, i)
i += 1
}
println("" + x.numer + "/" + x.denom)
</syntaxhighlight></font>
 
Оператор <tt>+</tt> принимает строку в качестве левого операнда и значение любого типа в качестве правого. Он возвращает результат преобразования своего правого операнда в строку и конкатенации левого операнда с этой строкой.
 
'''Наследование и переопределение.''' У каждого класса в Scala есть предок в иерархии (суперкласс), который расширяется данным классом. Если класс не упоминает предка в своем определении, предком неявно предполагается корневой тип <tt>scala.AnyRef</tt> (для реализаций Java этот тип — псевдоним <tt>java.lang.Object</tt>). Например, класс <tt>Rational</tt> можно эквивалентно определить так:
 
<font size=3><syntaxhighlight lang=Scala>
class Rational(n: Int, d: Int) extends AnyRef {
… // как прежде
}
</syntaxhighlight></font>
 
Класс наследует все члены своего суперкласса. Он также может переопределять (override) некоторые наследованные члены. Например, <tt>java.lang.Object</tt> определяет метод <tt>toString</tt>, который возвращает представление объекта в виде строки:
 
<font size=3><syntaxhighlight lang=Scala>
class Object {
def toString: String = …
}
</syntaxhighlight></font>
 
Реализация метода <tt>toString</tt> в <tt>Object</tt> формирует строку, состоящую из имени класса и номера объекта. Есть смысл переопределить этот метод для объектов, являющихся рациональными числами:
 
<font size=3><syntaxhighlight lang=Scala>
class Rational(n: Int, d: Int) extends AnyRef {
… // как прежде
override def toString = "" + numer + "/" + denom
}
</syntaxhighlight></font>
 
Заметьте, что в отличие от Java, в Scala переопределение определений должно предваряться модификатором <tt>'''override'''</tt>.
 
Если класс <tt>''A''</tt> расширяет (extends) класс <tt>''B''</tt>, то объекты типа <tt>''A''</tt> могут быть использованы везде, где ожидаются объекты типа <tt>''B''</tt>. В этом случае мы говорим, что класс <tt>''A''</tt> ''соответствует'' классу <tt>''B''</tt>. Например, <tt>Rational</tt> соответствует <tt>AnyRef</tt>, и корректно будет присвоить значение <tt>Rational</tt> переменной типа <tt>AnyRef</tt>:
 
<font size=3><syntaxhighlight lang=Scala>
var x: AnyRef = new Rational(1, 2)
</syntaxhighlight></font>
 
'''Методы без параметров.''' В отличие от Java, методы в Scala не обязательно должны принимать список параметров. Пример тому — метод <tt>square</tt>, представленный ниже. Этот метод вызывается просто упоминанием своего имени.
 
<font size=3><syntaxhighlight lang=Scala>
class Rational(n: Int, d: Int) extends AnyRef {
… // как прежде
def square = new Rational(numer*numer, denom*denom)
}
val r = new Rational(3, 4)
println(r.square) // выводит‘‘9/16’’*
</syntaxhighlight></font>
 
То есть, доступ к методам без параметров такой же, как к полям значений, таких как <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> в противном случае. Интерфейс множеств задан так:
 
<font size=3><syntaxhighlight lang=Scala>
abstract class IntSet {
def incl(x: Int): IntSet
def contains(x: Int): Boolean
}
</syntaxhighlight></font>
 
<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>IntSet</tt> попадает в эту категорию, можно определить его как трейт:
 
<font size=3><syntaxhighlight lang=Scala>
trait IntSet {
def incl(x: Int): IntSet
def contains(x: Int): Boolean
}
</syntaxhighlight></font>
 
'''Реализация абстрактных классов.''' Допустим, мы хотим реализовать множества в виде двоичных деревьев. Есть две разные формы деревьев: дерево для пустого множества и дерево, состоящее из целого числа и двух поддеревьев. Вот их реализация:
 
<font size=3><syntaxhighlight lang=Scala>
class EmptySet extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmptySet(x, new EmptySet, new EmptySet)
}
 
class NonEmptySet(elem: Int, left: IntSet, right: IntSet) extends IntSet {
def contains(x: Int): Boolean =
if (x < elem) left contains x
else if (x > elem) right contains x
else true
def incl(x: Int): IntSet =
if (x < elem) new NonEmptySet(elem, left incl x, right)
else if (x > elem) new NonEmptySet(elem, left, right incl x)
else this
}
</syntaxhighlight></font>
 
И <tt>EmptySet</tt>, и <tt>NonEmptySet</tt> расширяют класс <tt>IntSet</tt>. Из этого следует, что типы <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> для формирования объединения и пересечения двух множеств.
 
'''Упражнение 6.0.2''' Добавьте метод
 
<font size=3><syntaxhighlight lang=Scala>
def excl(x: Int)
</syntaxhighlight></font>
 
возвращающий данное множество без элемента <tt>x</tt>. Чтобы это сделать, полезно будет реализовать для множеств тестовый метод
 
<font size=3><syntaxhighlight lang=Scala>
def isEmpty: Boolean
</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>). Такое поведение прямо следует из нашей подстановочной модели вычислений. Например,
 
<pre>
(new EmptySet).contains(7)
 
-> (заменим 'contains' его телом из класса EmptySet)
 
false
</pre>
 
Или
 
<pre>
new NonEmptySet(7, new EmptySet, new EmptySet).contains(1)
 
-> (заменим 'contains' его телом из класса NonEmptySet)
 
if (1 < 7) new EmptySet contains 1
else if (1 > 7) new EmptySet contains 1
else true
 
-> (перепишем условное выражение)
 
new EmptySet contains 1
 
-> (заменим 'contains' его телом из класса EmptySet)
 
false
</pre>
 
Динамическое назначение методов аналогично вызовам функций высшего порядка. В обоих этих случаях только во время выполнения известно, какой код будет выполняться. Это сходство не является поверхностным. Действительно, Scala представляет каждую фукцию как объект (см. [[Scala в примерах#Функции|Параграф 8.6]]).
 
'''Объекты.''' В предыдущей реализации множеств целых чисел пустые множества были выражены при помощи <tt>'''new''' EmptySet</tt>, так что каждый раз, когда требовалось значение пустого множества, создавался новый объект. Мы могли бы избежать создания ненужных объектов, определив значение <tt>empty</tt> однажды и используя это значение вместо каждого <tt>'''new''' EmptySet</tt>. Например,
 
<font size=3><syntaxhighlight lang=Scala>
val EmptySetVal = new EmptySet
</syntaxhighlight></font>
 
Проблема этого подхода в том, что такое определение значения нелегально для высшего уровня в Scala, оно должно быть частью другого класса или объекта. К тому же определение класса <tt>EmptySet</tt> теперь выглядит несколько избыточным — зачем определять класс объектов, если нам нужен только один объект этого класса? Более прямой подход — использовать ''определение объекта''. Ниже представлен более прямолинейный вариант определения пустого множества:
 
<font size=3><syntaxhighlight lang=Scala>
object EmptySet extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmptySet(x, EmptySet, EmptySet)
}
</syntaxhighlight></font>
 
Синтаксис определения объекта следует синтаксису определения класса, у него есть необязательный пункт <tt>extends</tt> и необязательное тело. Как и в случае классов, пункт <tt>extends</tt> определяет наследованные члены объекта, а тело определяет переопределенные или новые члены. Определение объекта определяет только один объект, и нельзя создавать новые объекты с той же структурой при помощи <tt>'''new'''</tt>. Поэтому у определений объектов нет параметров конструкторов, которые могут присутствовать в определениях классов.
 
Определения объектов могут появляться в любом месте программ на Scala, включая высший уровень. Поскольку не существует фиксированного порядка выполнения сущностей высшего уровня в Scala, может возникнуть вопрос, когда в точности создается и инициализируется объект, определенный при помощи определения объекта. Объект впервые создается, когда впервые происходит доступ к одному из его членов. Такая стратегия называется ''ленивым вычислением''.
 
'''Стандартные классы.''' Scala — чисто объектно-ориентированный язык. Это значит, что каждое значение в Scala может быть рассмотрено как объект. На самом деле даже примитивные типы, как <tt>int</tt> или <tt>boolean</tt>, не рассматриваются специально. Они определены как псевдонимы типов классов Scala в модуле <tt>Predef</tt>:
 
<font size=3><syntaxhighlight lang=Scala>
type boolean = scala.Boolean
type int = scala.Int
type long = scala.Long
</syntaxhighlight></font>
 
Для эффективности компилятор обычно представляет значения типа <tt>scala.Int</tt> 32-битными целыми числами, значения <tt>scala.Boolean</tt> — двоичными значениями Java, и т.д. Но он преобразует эти специализированные представления в объекты, когда это требуется, например, когда значение примитивного типа <tt>Int</tt> передается в функцию с параметром типа <tt>AnyRef</tt>. Обратите внимание, что специальное представление примитивных значений это всего лишь оптимизация, оно не меняет смысла программы.
 
Вот спецификация класса <tt>Boolean</tt>:
 
<font size=3><syntaxhighlight lang=Scala>
package scala
abstract class Boolean {
def && (x: => Boolean): Boolean
def || (x: => Boolean): Boolean
def ! : Boolean
 
def == (x: Boolean) : Boolean
def != (x: Boolean) : Boolean
def < (x: Boolean) : Boolean
def > (x: Boolean) : Boolean
def <= (x: Boolean) : Boolean
def >= (x: Boolean) : Boolean
}
</syntaxhighlight></font>
 
Булевские значения определены при помощи только классов и объектов, без ссылок на встроенный тип булевских значений или чисел. Возможная реализация класса <tt>Boolean</tt> дана ниже. Это не реализация из страндартной бибилотеки Scala, стандартная реализация использует встроенные булевские значения из соображений эффективности.
 
<font size=3><syntaxhighlight lang=Scala>
package scala
abstract class Boolean {
def ifThenElse(thenpart: => Boolean, elsepart: => Boolean)
 
def && (x: => Boolean): Boolean = ifThenElse(x, false)
def || (x: => Boolean): Boolean = ifThenElse(true, x)
def ! : Boolean = ifThenElse(false, true)
 
def == (x: Boolean) : Boolean = ifThenElse(x, x.!)
def != (x: Boolean) : Boolean = ifThenElse(x.!, x)
def < (x: Boolean) : Boolean = ifThenElse(false, x)
def > (x: Boolean) : Boolean = ifThenElse(x.!, false)
def <= (x: Boolean) : Boolean = ifThenElse(x, true)
def >= (x: Boolean) : Boolean = ifThenElse(true, x.!)
}
case object True extends Boolean {
def ifThenElse(t: => Boolean, e: => Boolean) = t
}
case object False extends Boolean {
def ifThenElse(t: => Boolean, e: => Boolean) = e
}
</syntaxhighlight></font>
 
Вот частичная реализация класса <tt>Int</tt>:
 
<font size=3><syntaxhighlight lang=Scala>
package scala
abstract class Int extends AnyVal {
def toLong: Long
def toFloat: Float
def toDouble: Double
 
def + (that: Double): Double
def + (that: Float): Float
def + (that: Long): Long
def + (that: Int): Int // аналогично для -, *, /, %
 
def << (cnt: Int): Int // аналогично для >>, >>>
 
def & (that: Long): Long
def & (that: Int): Int // аналогично для |, ^
 
def == (that: Double): Boolean
def == (that: Float): Boolean
def == (that: Long): Boolean // аналогично для !=, <, >, <=, >=
}
</syntaxhighlight></font>
 
Класс <tt>Int</tt> можно в принципе реализовать, используя только объекты и классы, без ссылок на встроенный тип целых чисел. Чтобы понять, как это сделать, рассмотрим более простую задачу реализации типа <tt>Nat</tt> натуральных (т.е. неотрицательных) чисел. Вот определение абстрактного класса <tt>Nat</tt>:
 
<font size=3><syntaxhighlight lang=Scala>
abstract class Nat {
def isZero: Boolean
def predecessor: Nat
def successor: Nat
def + (that: Nat): Nat
def - (that: Nat): Nat
}
</syntaxhighlight></font>
 
Чтобы реализовать класс <tt>Nat</tt>, определим подобъект <tt>Zero</tt> и подкласс <tt>Succ</tt> (для следующего значения). Каждое число <tt>N</tt> представлено <tt>N</tt> применениями конструктора <tt>Succ</tt> к <tt>Zero</tt>:
 
<math>\underbrace{new\ Succ(\ ...\ new Succ}_{N}(Zero)\ ...\ )</math>
 
Реализация объекта <tt>Zero</tt> довольно прямолинейна:
 
<font size=3><syntaxhighlight lang=Scala>
object Zero extends Nat {
def isZero: Boolean = true
def predecessor: Nat = error("negative number")
def successor: Nat = new Succ(Zero)
def + (that: Nat): Nat = that
def - (that: Nat): Nat = if (that.isZero) Zero
else error("negative number")
}
</syntaxhighlight></font>
 
Реализации функций предыдущего элемента и вычитания в <tt>Zero</tt> выбрасывают исключение <tt>Error</tt>, которое завершает выполнение программы с переданным собщением об ошибке.
 
Вот реализация класса последующего элемента:
 
<font size=3><syntaxhighlight lang=Scala>
class Succ(x: Nat) extends Nat {
def isZero: Boolean = false
def predecessor: Nat = x
def successor: Nat = new Succ(this)
def + (that: Nat): Nat = x + that.successor
def - (that: Nat): Nat = if (that.isZero) this
else x - that.predecessor
}
</syntaxhighlight></font>
 
Обратите внимание на реализацию метода <tt>successor</tt>. Для создания последующего числа мы должны передать сам объект в качестве аргумента конструктора <tt>Succ</tt>. Ссылку на сам объект дает зарезервированное имя <tt>'''this'''</tt>.
 
Реализации <tt>+</tt> и <tt>-</tt> содержат рекурсивные вызовы с аргументом конструктора в качестве получателя. Рекурсия завершится, когда получатель будет объектом <tt>Zero</tt> (а это гарантированно случится в конце концов из-за того, как мы формируем числа).
 
'''Упражнение 6.0.3''' Напишите реализацию класса целых чисел <tt>Integer</tt>. Реализация должна поддерживать все операции класса <tt>Nat</tt> и два дополнительных метода:
 
<font size=3><syntaxhighlight lang=Scala>
def isPositive: Boolean def negate: Integer
</syntaxhighlight></font>
 
Первый метод должен возвращать <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> для отицательных.)
 
=== Элементы языка, представленные в этой главе ===
 
==== Типы ====
 
<pre>
Тип = ... | идентификатор
</pre>
 
Типы теперь могут быть произвольными идентификаторами, представляющими классы.
 
==== Выражения ====
 
<pre>
Выр = ... | Выр '.' Выр | 'new' Выр | 'this'
</pre>
 
Выражение теперь может быть созданием объекта, выбором члена <tt>m</tt> из выражения с объектным значением <tt>E</tt> (<tt>E.m</tt>) или зарезервированным именем <tt>'''this'''</tt>.
 
==== Определения и объявления ====
 
<pre>
Опр = ОпрФункции | ОпрЗначения | ОпрКласса | ОпрТрейта | ОпрОбъекта
ОпрКласса = ['abstract'] 'class' идентификатор ['(' [Параметры] ')']
['extends' Выр] ['{' {ОпрШаблона} '}]'
ОпрТрейта = 'trait' идентификатор ['extends' Выр] ['{' {ОпрШаблона} '}]'
ОпрШаблона = [Модификатор] (Опр | Объявл)
ОпрОбъекта = [Модификатор] Опр
Модификатор = 'private' | 'override'
Объявл = ОбъявлФункции | ОбъявлЗначения
ОбъявлФункции = 'def' идентификатор {'(' [Параметры] ')'} ':' Тип
ОбъявлЗначения = 'val' идентификатор ':' Тип
</pre>
 
Определение теперь может быть определением класса, трейта или объекта, как:
 
<font size=3><syntaxhighlight lang=Scala>
class C(params) extends B { defs }
trait T extends B { defs }
object O extends B { defs }
</syntaxhighlight></font>
 
Определения <tt>defs</tt> в классе, трейте или объекте могут быть предварены модификаторами <tt>'''private'''</tt> или <tt>'''override'''</tt>.
 
Абстрактные классы и трейты могут также содержать объявления. Так вводятся ''отложенные'' функции и значения с типами, но без предоставленных реализаций. Отложенные члены должны быть реализованы в подклассах перед созданием объектов абстрактного класса иди трейта.
 
= Case-классы и сопоставление с образцом =
</syntaxhighlight></font>
 
В первой строке создается новый массив строк. Во второй строке этот массив присваивается переменной <tt>y</tt> типа <tt>Array[Any]</tt>. В предположении, что массивы коварианты, это допустимая операция, поскольку <tt>Array[String]</tt> будет подтипом <tt>Array[Any]</tt>. Наконец, в последней строке в массив записывается действительноерациональное число. Это тоже допустимо, поскольку тип <tt>Rational</tt> является подтипом типа элементов массива <tt>y</tt>, <tt>Any</tt>. То есть, мы придем к сохранению действительногорационального числа в массив строк, что, очевидно, нарушает корректность типов.
 
Java решает эту проблему, вводя проверку во время выполнения на третьей строке, которая проверит, совместим ли записываемый элемент с типом элементов, для которого был создан массив. В примере выше мы видели, что этот тип элемента не обязательно является статическим типом элементов обновляемого массива. Если проверка не проходит, возникнет <tt>ArrayStoreException</tt>.
Помимо <tt>Function1</tt> есть также определения для функций любой другой арности (нынешняя реализация устанавливает некий разумный предел). То есть, для любого возможного числа параметров есть соответствующее определение. Синтаксис типа функции в Scala <tt>(T<sub>1</sub>, …, T<sub>n</sub>) => S</tt> — это просто сокращение для параметризованого типа <tt>Function''n''[T<sub>1</sub>, …, T<sub>n</sub>, S]</tt>.
 
Scala использует одинаковый синтаксис ''f(x)'' для применения функции, вне зависимости от того, является ли ''f'' методом или функциональным объектом. Это возможно благодаряиз-за следующемуследующей соглашениюконвенции: применение функции ''f(x)'', где ''f'' — объект (в противоположность методу) это сокращеннаяукороченная форма записизапись для ''f''.<tt>apply</tt>(''x''). Метод <tt>apply</tt>, принадлежащий типу функций, вставляется автоматически везде, где это необходимо.
 
Поэтому мы определяли операцию взятия элемента из массива в [[Scala в примерах#Аннотации вариантности|Параграфе 8.2]] при помощи метода <tt>apply</tt>. Для любого массивамасива <tt>a</tt> операция взятия элемента <tt>a(i)</tt> это сокращенная формаукороченная записизапись <tt>a.apply(i)</tt>.
 
Функции — это пример того, как полезны объявления контравариантных типовых параметров. Рассмотрим, например, следующий код:
 
= Списки =
Списки являются важной структурой данных во многих программах Scala.Список, содержащий элементы ''x<sub>1</sub>,. . . , x<sub>n</sub>'' записывается в виде ''List(x<sub>1</sub>,. . . , x<sub>n</sub>)''.
Например:
<font size=3><syntaxhighlight lang=Scala>
val fruit = List("apples", "oranges", "pears")
val nums = List(1, 2, 3, 4)
val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
val empty = List()
</syntaxhighlight></font>
Списки похожи на массивы в таких языках, как C или Java, но имеется три существенных отличия. Во-первых, списки Scala являются неизменяемыми. То есть, элементы списка не могут быть изменены присваиванием. Во-вторых, списки имеют рекурсивную структуру, в то время как массивы являются плоскими. В-третьих, списки поддерживают гораздо более богатый набор операций, чем это обычно характерно для массивов.
== Использование списков ==
== Определение класса List I: методы первого порядка ==
83

правки