Язык программирования Scala

Автор работы: Пользователь скрыл имя, 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

Файлы: 1 файл

Курсовая работа СКАЛА.docx

— 62.42 Кб (Скачать файл)

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].

Вариантность (Variance).

Комбинация 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) структур данных ковариантность безопасна и очень естественна. Например, неизменяемый список целых  чисел можно естественно представить  как специальный случай списка элементов  типа Any. Есть также случаи, где желательна контрвариантность (contravariance) параметров. В качестве примеров можно привести каналы вывода Chan[T], с операцией записи, принимающей параметр Т. Здесь может  понадобиться Chan[S] <: Chan[T] в случае, когда T <: S.

Scala позволяет объявить  вариантность параметров типов  класса с помощью знаков + и  -. "+" перед именем параметра  указывает, что конструктор ковариантен  по отношению к параметру, "-" указывает, что он контрвариантен, а отсутствие префикса говорит  об отсутствии вариантности.

Система типов Scala обеспечивает опознание аннотаций вариантности с помощью отслеживания позиций  применения параметров типов. Если параметр типа встречается в качестве типа возвращаемого значения функции  или типа, доступного только на чтение свойства, он рассматривается как  ковариантный. Если параметр типа встречается  в качестве параметра метода или  изменяемого свойства/поля, то он рассматривается  как контрвариантный. Параметр типа также рассматривается как контрвариантный  и при явном сужении трактовки  типа. Аргументы невариантных параметров типов всегда находятся в невариантной позиции. Ковариантные и контрвариантные позиции меняются местами внутри аргумента типа, соответствующего контрвариантному параметру. Система типов гарантирует, что ковариантные (и соответственно, контрвариантные) параметры типов используются только в ковариантных (контрвариантных) позициях.

Ковариантные подстановочные знаки могут использоваться в  любом выражении типа. Однако члены, где переменная типа не появляется в ковариантной позиции, будут "забыты". Например, тип GenCell<? extends Number> будет  иметь только метод get типа Number, тогда  как метод Set, в котором параметр типа GenCell встречается контрвариантно, будет "забыт".

В ранних версиях Scala мы тоже экспериментировали с подходами, похожими на подстановочные знаки. На первый взгляд, эта схема выглядит привлекательно благодаря своей гибкости. Один класс  в ней может иметь как ковариантные, так и невариантные фрагменты; пользователь делает выбор между ними, помещая  или пропуская подстановочные знаки. Однако это увеличение гибкости имеет  цену, поскольку теперь пользователь класса, а не его создатель должен проверять, что вариантность не приводит противоречиям. Мы обнаружили, что на практике довольно сложно добиться непротиворечивости в использовании аннотаций, так  что ошибки типов случались довольно часто. Наоборот, задание трактовки  типов в декларации класса оказалось  очень полезным для улучшения  дизайна класса; например, такие  трактовки отлично помогают определить, какие методы должны быть обобщены с расширением трактовки типа. Более того, mixin-композиция Scala (см. соотв. раздел) делает относительно простым  явное разложение классов на ковариантные и контрвариантные фрагменты; используемая в Java схема одиночного наследования с интерфейсами, скорее всего, значительно  затруднила бы это. По этим причинам поздние  версии Scala используют трактовку типов  в декларациях.

Абстрактные члены

Объектно-ориентированная  абстракция может использоваться в Scala как альтернатива функциональной абстракции. Например, вот версия типа “cell”, использующего объектно-ориентированную  абстракцию:abstract class AbsCell

{

  type T;

  val init: T;

  private var value: T = init;

  def get: T = value;

  def set(x: T) : unit = { value = x }

}

Класс AbsCell не определяет ни параметров типов, ни обычных параметров. Вместо этого он содержит абстрактный  член типа, Т, и абстрактную переменную init. Экземпляры этого класса могут  быть созданы с помощью реализации этих абстрактных членов с конкретными  определениями.

Семейный  полиморфизм (family polymorphism) и self-типы.

Концепция абстрактных типов Scala чрезвычайно хорошо подходит для  моделирования семейств типов, которые  изменяются вместе ковариантно. Эту  концепцию мы назвали "семейным полиморфизмом". В качестве примера рассмотрим паттерн "наблюдатель", применяемый в  данном случае для реализации модели "издатель/подписчик". Есть два  класса участников – субъекты и  наблюдатели. Субъекты определяют метод subscribe, с помощью которого регистрируются наблюдатели. Они также определяют метод publish, оповещающий всех зарегистрированных наблюдателей. Оповещение производится с помощью вызова метода notify, определяемого  всеми наблюдателями. Обычно publish вызывается, когда изменяется состояние субъекта. С субъектом может быть связано  несколько наблюдателей, а каждый наблюдатель может отслеживать  нескольких субъектов. Метод subscribe принимает  идентификатор регистрируемого  наблюдателя как параметр, а метод  наблюдателя notify принимает в качестве параметра субъекта, производящего  оповещение. Таким образом, субъекты и наблюдатели ссылаются друг на друга в сигнатурах методов.

Моделирование обобщенных (generic) типов с помощью  абстрактных типов

Наличие двух абстракций типов  в одном языке поднимает вопрос о сложности языка – нельзя ли было обойтись одной? В этом разделе  мы покажем, что функциональная абстракция типов (ака обобщенные типы) может  в принципе быть смоделирована с  помощью объектно-ориентированной  абстракции типов (иначе абстрактных  типов). Одну форму можно трансформировать в другую. Идея трансформации в общих чертах описана ниже.

Предположим, что у нас  есть параметризованный класс C с  параметром типа Т (код напрямую обобщается в множественные параметры типов). Трансформация состоит из четырех  частей, затрагивающих определение  класса, создание экземпляров класса, вызовы конструкторов базового класса и экземпляры типов класса.

1. Определение класса C трансформируется  так:class C

{

  type t;

  /* остальная часть  класса */

}

Таким образом, параметры  исходного класса моделируются с  помощью абстрактных членов трансформированного  класса. Если параметр типа t имеет сужения  и/или расширения, они переносятся  в определение абстрактного типа. Вариантность параметров типа не переносится; вместо этого вариантность влияет на формирование типов (см. пункт 4).

2. Создание каждого экземпляра new C[T] с аргументом типа Т трансформируется  в:new C { type t = T }

3. Если C[T] выступает в  роли конструктора суперкласса,  его классы-наследники дополняются  определением:type t = T

4. Каждый из типов C[T] трансформируется в один из  следующих типов, каждый из  которых дополняет класс С  уточнением:

C { type t = T } если t объявлен  не вариантным,

C { type t <: T } если t объявлен  ковариантным,

C { type t >: T } если t объявлен  контрвариантным.

Такой код работает, если не встречается конфликтов имен. Поскольку  имя параметра становится членом класса, оно может конфликтовать  с другими членами, включая унаследованные члены, сгенерированные по именам параметров базовых классов. Этих конфликтов имен можно избежать переименованием, например, дополнением каждого имени уникальным номером.

Возможность трансформации  из одного стиля абстракции в другой полезна, так как снижает концептуальную сложность языка. В случае Scala обобщенные типы становятся не более, чем "синтаксическим сахаром", который можно устранить  трансформацией в абстрактные типы. Однако возникает вопрос, насколько  обосновано наличие этого синтаксического  сахара, и нельзя ли обойтись одними абстрактными типами, то есть ограничиться синтаксически меньшим языком. Есть два аргумента за включение обобщенных типов в Scala. Во-первых, трансформацию  в абстрактные типы не так уж просто писать вручную. Это приводит потерь выразительности, и есть также проблема случайных конфликтов имен между  именами абстрактных типов, эмулирующих  параметры типов. Во-вторых, обобщенные и абстрактные типы обычно играют в Scala-программах различные роли. Обобщенные типы обычно используют, когда нужна  только реализация экземпляра типа, а  абстрактные типы – когда нужна  ссылка на абстрактный тип из клиентского  кода. Последнее встречается, в частности, в двух ситуациях. Может понадобиться спрятать точное определение члена  типа от клиентского кода, чтобы  получить нечто вроде инкапсуляции, известной по модульным системам в SML-стиле. Или же может потребоваться  переопределить тип ковариантно  в подклассах, чтобы получить семейный полиморфизм.

Можно ли пойти другим путем  и перекодировать абстрактные типы в обобщенные? Оказывается, это значительно  труднее, и требует полного переписывания  программы. Это было показано в исследованиях  в области модульных систем, где  доступны оба вида абстракции [21]. На самом деле такая сложность неудивительна, если рассматривать проблему с точки  зрения основ теории типов обеих  систем. Обобщенные типы (без F-ограничений) могут выражаться в System F<: [описанной  в 9], тогда как абстрактные типы требуют системы, основанной на зависимых  типах. Последние, в общем, выразительнее  предыдущих, например, ?Obj с его зависимыми от пути типами позволяет закодировать F<:.

Композиция

Повторное использование классов

Повторное использование  существующих компонентов ПО при  создании новых систем имеет множество  преимуществ: оно уменьшает стоимость  и время разработки, снижает требования к поддержке, а также повышает надежность ПО.

Поэтому объектно-ориентированные  языки программирования содержат механизмы, обеспечивающие возможность повторного использования существующих программных  сущностей, например, классов. В этом разделе рассматривается работа механизмов повторного использования  кода в 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 работает со следующей абстракцией итератора:trait Iterator[T]

{

  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.

Информация о работе Язык программирования Scala