Автор работы: Пользователь скрыл имя, 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
В качестве примера объявления
и применения операторов, определяемых
пользователем, рассмотрим следующую
реализацию класса Nat для натуральных
чисел. Этот класс (очень неэффективно)
представляет числа как экземпляры
двух классов – Zero и Succ. Число N будет
представлено как new SuccN(Zero). Начнем реализацию
с trait-а, определяющего интерфейс
натуральных чисел. Пока будем рассматривать
trait-ы как абстрактные
{
def isZero: boolean;
def pred: Nat;
def succ: Nat = newSucc(this);
def +(x: Nat) : Nat = if (x.isZero) this else succ + x.pred;
def -(x: Nat) : Nat = if (x.isZero) this else predx.pred;
}
Заметьте, что Scala позволяет определять методы без параметров, такие, как isZero, pred и succ в классе Nat. Такие методы вызываются каждый раз, когда выбрано их имя, никакого списка аргументов не передается. Заметьте также, что члены абстрактного класса идентифицируются синтаксически, поскольку у них нет определения. Дополнительный модификатор abstract не требуется.
Теперь расширим trait Nat singleton-объектом Zero и классом, представляющим последующие элементы, Succ.object Zero extends Nat
{
def isZero: boolean = true;
def pred: Nat = throw new Error("Zero.pred");
}
class Succ(n: Nat) extends Nat
{
def isZero: boolean = false;
def pred: Nat = n;
}
Класс Succ показывает разницу в синтаксисе определения классов между Scala и Java. В Scala параметры конструктора следуют за именем класса; отдельное определение конструктора класса в теле Succ не нужно. Этот конструктор называется первичным конструктором (primary constructor); при вызове первичного конструктора во время создания экземпляра класса исполняется все тело класса. На тот случай, когда нужно более одного конструктора, предусмотрен синтаксис вторичных конструкторов (secondary constructors).
Возможность создавать пользовательские инфикс-операторы ставит вопрос об их относительном старшинстве и ассоциативности. Одной возможностью может быть наличие “fixity”-деклараций в стиле Haskell или SML, где пользователи могут объявлять эти свойства оператора индивидуально. Однако такие декларации плохо сочетаются с модульным программированием. В Scala выбрана более простая схема с фиксированными старшинством и ассоциативностью. Старшинство инфикс-оператора определяется по его первой букве; это совпадает со старшинством операторов, принятым в С и Java для операторов, начинающихся со знака оператора, используемого в этих языках. Вот список операторов, отсортированный по возрастанию:
(Все буквы)
|
^
&
< >
= !
:
+ -
* / %
(все остальные
Операторы обычно лево-ассоциативны,
то есть x+y+z интерпретируется как (x+y)+z.
Единственное исключение из этого правила
– операторы, заканчивающиеся двоеточием.
Они считаются право-
Некоторые операторы в Scala не всегда вычисляют свой аргумент; примером могут служить стандартные булевы операторы && и ||. Такие операторы можно также представить как методы, поскольку Scala позволяет передавать аргументы по имени. Например, вот сигнатура пользовательского trait-а Bool, имитирующего предопределенные булевы значения:trait Bool
{
def &&(defx: Bool) : Bool;
def ||(defx: Bool) : Bool;
}
В этом trait-е формальный параметр
методов || и && предваряется def. Реальные
аргументы этих параметров передаются
в не вычисленной форме. Аргументы
вычисляются при каждом упоминании
имени формального параметра (то
есть формальный параметр ведет себя
как функция без параметров). (В
среде функционального
Вот два канонических экземпляра класса Bool:object False extends Bool
{
def &&(defx: Bool) : Bool = this;
def ||(defx: Bool) : Bool = x;
}
object True extends Bool
{
def &&(defx: Bool) : Bool = x;
def ||(defx: Bool) : Bool = this;
}
Как можно увидеть из этих реализаций, правый операнд операции && (и соответственно ||) вычисляется, только если левый операнд – объект True(False).
Как показывают примеры из
этого раздела, в Scala любой оператор
можно определить как метод и
рассматривать любую операцию как
вызов метода. В целях эффективности
компилятор Scala транслирует операции
над типами-значениями прямо в
коды примитивных инструкций; это, однако,
полностью прозрачно для
Если каждая операция в Scala – это вызов метода, то как насчет разыменования и присваивания переменных? На самом деле, при работе с членами классов эти операции также расцениваются как вызовы методов. Для каждого определения переменной varx:T в классе Scala определяет методы setter и getter:def x: T;
def x_ = (newval: T) : unit;
Эти методы ссылаются на изменяемую ячейку памяти (и обновляют ее), недоступную из Scala-программ напрямую. Каждое упоминание имени х в выражении, таким образом, становится вызовом лишенного параметров метода х. Далее, каждое присваивание х=е интерпретируется как вызов метода x_=(e).
В Scala интерпретация обращения к переменным как вызовов методов позволяет определять свойства (в смысле C#). Например, следующий класс Celsius определяет свойство degree, которое может иметь значения не меньше -273.class Celsius
{
private var d: int = 0;
def degree: int = d;
def degree_ = (x: int) : unit = if (x >= -273) d = x
}
Клиенты могут использовать пару методов, определенных в классе Celsius, как если бы объявлялась переменная:val c = new Celsius;
c.degree = c.degree - 1
Scala – это функциональный
язык в том смысле, что каждая
функция – это значение. Он
предоставляет легковесный
Чтобы проиллюстрировать
использование функций как
{
var i: int = 0;
while (i < xs.length && !p(xs(i)))
i = i + 1;
i < xs.length
}
Тип элемента массива – произвольный; это выражается параметром типа [T] метода exists (параметры типов подробно рассматриваются ниже). Предикат для проверки – тоже произвольный; это выражается параметром p метода exists. Тип p – это тип функции T=>boolean, значениями которой являются все функции из домена Т в boolean. Функции параметры можно применять так же, как обычные функции; примером может служить применение p в условии цикла while. Функции, принимающие другие функции в качестве аргументов, или возвращающие их в качестве результата, называются функциями высшего порядка.
Имея функцию exists, мы можем определить функцию forall в терминах этой функции с помощью двойного отрицания: предикат выполняется для всех значений массива, если не существует элемента, для которого он не выполняется (фактически означает, что (для любого x из X P(x) = true) ( (не существует x из X, такого что P(x) = false)). Это выражается следующей функцией forall:def forall[T](xs: Array[T], p: T => boolean) =
{
def not_p(x: T) = !p(x);
!exists(xs, not_p)
}
Функция forall определяет вложенную функцию not_p, которая отрицает предикат параметра p. Вложенные функции могут обращаться к параметрам и локальным переменным определенным в их окружении; например, not_p обращается к параметру р функции forall.
Можно определить функцию, не давая ей имени; это используется в следующей, более короткой версии forall:def forall[T](xs: Array[T], p: T => boolean) = !exists(xs, x: T => !p(x));
Здесь x:T=>!p(x) определяет анонимную функцию, которая отображает свой параметр x типа T на !p(x).
Используя exists и forall, мы можем определить функцию hasZeroRow, которая проверяет, есть ли в данной двумерной матрице целых чисел строка, состоящая из одних нулей.def hasZeroRow(matrix: Array[Array[int]]) =
exists(matrix, row: Array[int] => forall(row, 0==));
Выражение forall(row, 0==) проверяет, состоит ли строка только из нулей. Здесь метод == числа 0 передается как аргумент, соответствующий параметру предиката p. Это показывает, что сами методы могут использоваться в Scala как значения; это похоже на концепцию делегатов в C#.
Если методы – это значения,
а значения – это объекты, то сами
методы также являются объектами. На
самом деле, синтаксис типов и
значений функций – это просто
"синтаксический сахар" для определенных
типов классов и экземпляров
классов. Тип функции S=>T эквивалентен
параметризованному типу класса scala.Function1[S,
T], который определен в
trait Function1[S, T]
{
def apply(x: S) : T
}
Аналогичные конвенции существуют для функций, имеющих более одного аргумента. В общем случае, N-арный тип, (T1, T2, ..., Tn)=>T, интерпретируется как Functionn[T1, T2, ..., Tn, T]. Таким образом, функции интерпретируются как объекты с методами apply. Например, анонимная функция “incrementer” x:int=>x+1 будет развернута в экземпляр Function1:new Function1[int, int]
{
def apply(x: int) : int = x + 1
}
И наоборот, если значение функционального типа применяется к некоторым аргументам, неявно вставляется метод типа apply. Например, для p типа Function1[S, T] использование p(x) расширяется до p.apply(x).
Методы высшего порядка
широко применяются в обработке
последовательностей. В библиотеке
Scala определено несколько видов
Применение этих двух функций иллюстрирует следующая функция, sqrts, принимающая список чисел с двойной точностью и возвращающая список, состоящий из квадратных корней всех неотрицательных элементов xs.def sqrts(xs: List[double]) : List[double] =
xs filter (0<=) map Math.sqrt;
Заметьте, что Math.sqrt происходит из Java-класса. Такие методы могут быть переданы функциям высшего порядка так же, как методы, определенные в Scala.
Scala предоставляет специальный
синтаксис для более
for(val x <- xs; 0 <= x)
yield Math.sqrt;
Здесь "val x <- xs" – это
генератор, который производит последовательность
значений, а "0 <= x" – фильтр, исключающий
из рассмотрения некоторые из получаемых
значений. Comprehension возвращает другую последовательность,
образованную значениями, выданными
частью yield. Comprehension может содержать
несколько генераторов и
For comprehensions выражаются через
комбинации методов высшего
Сила for comprehensions в том, что они не привязаны к конкретному типу данных. Они могут быть сконструированы над любым несущим типом, который определяет подходящие методы map, flatMap и filter. В это число входят все типы последовательностей, необязательные значения, интерфейсы баз данных, а также несколько других типов. Пользователи Scala могут применять forcomprehensions к собственным типам, если они поддерживают нужные методы.
Циклы for в Scala похожи на comprehensions. Они отображаются на комбинации методов foreach и filter. Например, цикл for:for (val arg <- args)...
из листинга 1 отображается на:args foreach (arg => ...)
Важная проблема компонентных
систем – как абстрагироваться от
требуемых компонентов. В языках
программирования есть два основных
способа абстрагирования: параметризация
и абстрактные члены. Первый способ,
как правило, применяется для
функций, а второй, как правило, для
объектов. Java традиционно поддерживала
функциональную абстракцию для значений
и объектно-ориентированную
Scala поддерживает оба стиля
абстракций и для типов, и
для значений. И типы, и значения
могут быть как параметрами,
так и абстрактными членами.
В этом разделе
Следующий класс определяет простой trait, реализующий некую ячейку, доступную на чтение и запись.class GenCell[T](init: T)
{
private var value: T = init;
def get: T = value;
def set(x: T) : unit = { value = x }
}
Класс абстрагирует некоторое значение, тип которого определяется параметром типа T. Можно также сказать, что класс GenCell – generic-класс.
Как и классы, методы также могут иметь параметры типов. Следующий метод меняет местами содержимое двух ячеек. Ячейки должны содержать однотипные значения.def swap[T](x: GenCell[T], y: GenCell[T]) : unit =
{
val t = x.get;
x.set(y.get);
y.set(t)
}
Следующий код создает две ячейки, содержащие целые числа, и затем меняет местами их содержимое.val x: GenCell[int] = new GenCell[int](1);
val y: GenCell[int] = new GenCell[int](2);
swap[int](x, y)
Фактические аргументы типа указываются в квадратных скобках; они заменяют формальные параметры конструктора или методов класса. Scala поддерживает сложную систему выведения типа, которая разрешает опускать фактические аргументы типа в обоих случаях. Для метода или конструктора аргументы типа могут выводиться из ожидаемого типа результата или типов аргументов с помощью локального выведения типов. Следовательно, пример, приведенный выше, можно переписать без любых аргументов типа:valx = new GenCell(1);