Автор работы: Пользователь скрыл имя, 21 Мая 2013 в 21:29, курсовая работа
Язык Scala стал результатом исследований, направленных на разработку более хорошей языковой поддержки компонентного ПО. С помощью Scala я хотела бы проверить две гипотезы.
Введение 3
История 5
Истоки дизайна 5
Ключевые аспекты языка 5
Объектно-ориентированный язык 7
Функциональный язык 7
Java-подобный язык 7
Классы 9
Операции 11
Переменные и свойства 15
Операции – это объекты 15
Методы и функциональные значения 16
Функции – это объекты 17
Последовательности 18
For Comprehensions 18
Абстракции 19
Функциональная абстракция 20
Вариантность (Variance). 21
Абстрактные члены 23
Семейный полиморфизм (family polymorphism) и self-типы. 24
Моделирование обобщенных (generic) типов с помощью абстрактных типов 24
Композиция 27
Повторное использование классов 27
Наследование. 28
Trait 31
Декомпозиция 32
Объектно-ориентированная декомпозиция 32
Модель данных 32
Автономные компоненты 32
Адаптация компонентов 33
Виды (views) 33
Границы видов 34
Заключение 35
Список литературы 36
valy = new GenCell(2);
swap(x, y)
Трактовка параметров (Parameter bounds). Рассмотрим метод updateMax, устанавливающий для ячейки максимальное значение из значений свойства и параметра. Определим updateMax так, чтобы он работал с любыми значениями ячейки, допускающими использование функции сравнения “<”, определенной в trait-е Ordered. На минуточку представим, что этот trait определен следующим образом (более совершенная версия этого trait-а содержится в стандартной библиотеке Scala).trait Ordered[T]
{
def <(x:T) : boolean;
}
Метод UpdateMax может быть определен обобщенным образом с использованием полиморфизма c ограничениями (bounded-полиморфизма):def updateMax[T <: Ordered[T]](c:GenCell[T], x:T) =
if (c.get < x) c.set(x)
Здесь описание параметра типа [T <: Ordered[T]] вводит сужающую трактовку параметра типа (bounded type parameter). Он ограничен теми типами Т, которые являются подтипами Ordered[T]. Поэтому метод < класса Ordered может быть применен к аргументам типа Т. Суженный параметр (т.е. параметр, описанный с ограничениями) типа может сам появляться как часть другой трактовки параметра, т.е. Scala поддерживает так называемый F-ограниченный полиморфизм, описанный в [8].
Комбинация generic-ов и subtyping-а (выделения подтипов) в языке поднимает вопрос об их взаимодействии. Если C – конструктор типа, а S – подтип T, получается ли, что C[S] – подтип C[T]? Конструкторы типа, имеющие такое свойство, называют ковариантными. Конструктор типа GenCell, очевидно, не должен быть ковариантным; иначе можно было бы создать следующую программу, приводящую во время выполнения к ошибке:val x: GenCell[String] = new GenCell[String];
val y: GenCell[Any] = x; // недопустимо!
y.set(1);
val z: String = y.get
Присутствие изменяемой (mutable) переменной в GenCell делает ковариантность опасной. На самом деле, GenCell[String] – это не особый случай GenCell[Any], так как есть вещи, которые можно сделать с GenCell[Any], но нельзя сделать с GenCell[String]; например, задать такой ячейке целочисленное значение.
С другой стороны, для не
изменяющихся (immutable) структур данных ковариантность
безопасна и очень естественна.
Например, неизменяемый список целых
чисел можно естественно
Scala позволяет объявить
вариантность параметров типов
класса с помощью знаков + и
-. "+" перед именем параметра
указывает, что конструктор
Система типов Scala обеспечивает
опознание аннотаций
Ковариантные подстановочные знаки могут использоваться в любом выражении типа. Однако члены, где переменная типа не появляется в ковариантной позиции, будут "забыты". Например, тип GenCell<? extends Number> будет иметь только метод get типа Number, тогда как метод Set, в котором параметр типа GenCell встречается контрвариантно, будет "забыт".
В ранних версиях Scala мы тоже
экспериментировали с подходами, похожими
на подстановочные знаки. На первый взгляд,
эта схема выглядит привлекательно
благодаря своей гибкости. Один класс
в ней может иметь как
Объектно-ориентированная
абстракция может использоваться в
Scala как альтернатива функциональной
абстракции. Например, вот версия типа
“cell”, использующего объектно-
{
type T;
val init: T;
private var value: T = init;
def get: T = value;
def set(x: T) : unit = { value = x }
}
Класс AbsCell не определяет ни параметров типов, ни обычных параметров. Вместо этого он содержит абстрактный член типа, Т, и абстрактную переменную init. Экземпляры этого класса могут быть созданы с помощью реализации этих абстрактных членов с конкретными определениями.
Концепция абстрактных типов Scala чрезвычайно хорошо подходит для моделирования семейств типов, которые изменяются вместе ковариантно. Эту концепцию мы назвали "семейным полиморфизмом". В качестве примера рассмотрим паттерн "наблюдатель", применяемый в данном случае для реализации модели "издатель/подписчик". Есть два класса участников – субъекты и наблюдатели. Субъекты определяют метод subscribe, с помощью которого регистрируются наблюдатели. Они также определяют метод publish, оповещающий всех зарегистрированных наблюдателей. Оповещение производится с помощью вызова метода notify, определяемого всеми наблюдателями. Обычно publish вызывается, когда изменяется состояние субъекта. С субъектом может быть связано несколько наблюдателей, а каждый наблюдатель может отслеживать нескольких субъектов. Метод subscribe принимает идентификатор регистрируемого наблюдателя как параметр, а метод наблюдателя notify принимает в качестве параметра субъекта, производящего оповещение. Таким образом, субъекты и наблюдатели ссылаются друг на друга в сигнатурах методов.
Наличие двух абстракций типов
в одном языке поднимает вопрос
о сложности языка – нельзя
ли было обойтись одной? В этом разделе
мы покажем, что функциональная абстракция
типов (ака обобщенные типы) может
в принципе быть смоделирована с
помощью объектно-
Предположим, что у нас
есть параметризованный класс C с
параметром типа Т (код напрямую обобщается
в множественные параметры
1. Определение класса C трансформируется так:class C
{
type t;
/* остальная часть класса */
}
Таким образом, параметры исходного класса моделируются с помощью абстрактных членов трансформированного класса. Если параметр типа t имеет сужения и/или расширения, они переносятся в определение абстрактного типа. Вариантность параметров типа не переносится; вместо этого вариантность влияет на формирование типов (см. пункт 4).
2. Создание каждого экземпляра
new C[T] с аргументом типа Т
3. Если C[T] выступает в
роли конструктора суперкласса,
4. Каждый из типов C[T] трансформируется в один из следующих типов, каждый из которых дополняет класс С уточнением:
C { type t = T } если t объявлен не вариантным,
C { type t <: T } если t объявлен ковариантным,
C { type t >: T } если t объявлен контрвариантным.
Такой код работает, если не встречается конфликтов имен. Поскольку имя параметра становится членом класса, оно может конфликтовать с другими членами, включая унаследованные члены, сгенерированные по именам параметров базовых классов. Этих конфликтов имен можно избежать переименованием, например, дополнением каждого имени уникальным номером.
Возможность трансформации из одного стиля абстракции в другой полезна, так как снижает концептуальную сложность языка. В случае Scala обобщенные типы становятся не более, чем "синтаксическим сахаром", который можно устранить трансформацией в абстрактные типы. Однако возникает вопрос, насколько обосновано наличие этого синтаксического сахара, и нельзя ли обойтись одними абстрактными типами, то есть ограничиться синтаксически меньшим языком. Есть два аргумента за включение обобщенных типов в Scala. Во-первых, трансформацию в абстрактные типы не так уж просто писать вручную. Это приводит потерь выразительности, и есть также проблема случайных конфликтов имен между именами абстрактных типов, эмулирующих параметры типов. Во-вторых, обобщенные и абстрактные типы обычно играют в Scala-программах различные роли. Обобщенные типы обычно используют, когда нужна только реализация экземпляра типа, а абстрактные типы – когда нужна ссылка на абстрактный тип из клиентского кода. Последнее встречается, в частности, в двух ситуациях. Может понадобиться спрятать точное определение члена типа от клиентского кода, чтобы получить нечто вроде инкапсуляции, известной по модульным системам в SML-стиле. Или же может потребоваться переопределить тип ковариантно в подклассах, чтобы получить семейный полиморфизм.
Можно ли пойти другим путем
и перекодировать абстрактные типы
в обобщенные? Оказывается, это значительно
труднее, и требует полного
Повторное использование существующих компонентов ПО при создании новых систем имеет множество преимуществ: оно уменьшает стоимость и время разработки, снижает требования к поддержке, а также повышает надежность ПО.
Поэтому объектно-ориентированные языки программирования содержат механизмы, обеспечивающие возможность повторного использования существующих программных сущностей, например, классов. В этом разделе рассматривается работа механизмов повторного использования кода в Scala на примере кода, приведенного ниже. Этот код определяет обобщенный класс Buffer[T] для сборки последовательностей элементов.class Buffer[T]
{
var xs: List[T] = Nil;
def add(elem: T) : Unit = xs = elem::xs;
def elements: Iterator[T] = new BufferIterator;
class BufferIterator extends Iterator[T]
{
var ys = xs;
def hasNext: Boolean = !ys.isEmpty;
def next: T =
{
val res = ys.head;
ys = ys.tail;
res
}
}
}
Реализация класса Buffer работает
со следующей абстракцией
{
def hasNext: Boolean;
def next: T;
}
Как и в большинстве распространенных объектно-ориентированных языков, главный механизм повторного использования классов в Scala основывается на одиночном наследовании; то есть программисты могут специализировать классы, создавая подклассы. Чтобы расширить класс Buffer дополнительными методами forall и exists, можно, например, создать подкласс IterableBuffer, определяющий новую функциональность:class IterableBuffer[T] extends Buffer[T]
{
def forall(p: T => Boolean) : Boolean =
{
val it = elements;
var res = true;
while (res && it.hasNext)
res = p(it.next);
res
}
def exists(p: T => Boolean) : Boolean =
{
val it = elements;
var res = false;
while (!res && it.hasNext)
res=p(it.next);
res
}
}
Композиция типов (mixin-композиция). Проблема приведенного выше кода состоит в ограниченной возможности повторного использования. Представьте, что существует независимое расширение класса Buffer, моделирующее стек:class Stack[T] extends Buffer[T]
{
def push(elem: T) : Unit = add(elem);
def pop : T =
{
val y = xs.head;
xs = xs.tail;
y
}
}
Одиночное наследование не позволяет повторно использовать существующие определения методов forall и exists совместно со стеком. Поэтому Scala предоставляет механизм композиции mixin-классов, позволяющий программистам повторно использовать различия в определениях классов (то есть использовать все новые определения, которые не были унаследованы, в определении нового класса). Этот механизм позволяет объединить IterableBuffer и Stack:class IterableStack[T] extends Stack[T] with IterableBuffer[T].
Этот код определяет класс IterableStack[T], который наследует все определения из Stack[T], и, кроме того, включает новые определения из IterableBuffer[T]. Смешивание класса С с другим классом D законно, только если суперкласс D – подкласс суперкласса C. Так, mixin-композиция в приведенном выше коде вполне корректна, так как суперкласс класса IterableStack – подкласс суперкласса IterableBuffer.