Язык программирования 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 Кб (Скачать файл)

В качестве примера объявления и применения операторов, определяемых пользователем, рассмотрим следующую  реализацию класса Nat для натуральных  чисел. Этот класс (очень неэффективно) представляет числа как экземпляры двух классов – Zero и Succ. Число N будет  представлено как new SuccN(Zero). Начнем реализацию с trait-а, определяющего интерфейс  натуральных чисел. Пока будем рассматривать trait-ы как абстрактные классы, ниже о них будет сказано подробнее. Согласно определению trait-а Nat, натуральные  числа предоставляют два абстрактных  метода, isZero и pred, и три обычных  метода succ, + и -.trait Nat

{

  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. Единственное исключение из этого правила  – операторы, заканчивающиеся двоеточием. Они считаются право-ассоциативными. Примером служит образующий списки оператор ::. Так, x::y::zs интерпретируется как x::(y::zs). Право-ассоциативные операторы, кроме  того, по-другому интерпретируются в смысле просмотра методов. Если у обычных операторов в качестве приемника используется левый операнд, то у право-ассоциативных – правый. Например, последовательность создания списка x::y::zs эквивалентна zs.::(y).::(x). На самом деле, :: реализован как метод Scala-класса List, предпосылающего данный аргумент списку приемников и возвращающего  результирующий список.

Некоторые операторы в 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 – это функциональный  язык в том смысле, что каждая  функция – это значение. Он  предоставляет легковесный синтаксис  для определения анонимных и  карринговых функций (функций,  для которых допустимо частичное  определение. Def sum a b = a + b – пример  такой функции, мы можем определить  функцию inc путем частичного применения add: def inc = add 10 ), а также поддерживает вложенные функции.

Методы  и функциональные значения

Чтобы проиллюстрировать  использование функций как значений, рассмотрим функцию exists, проверяющую, содержит ли некий массив элемент, удовлетворяющий  заданному предикату:def exists[T](xs: Array[T], p:T => boolean) =

{

  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], который определен в стандартной  библиотеке Scala так:package scala;

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 определено несколько видов последовательностей, среди них списки, потоки и итераторы. Все типы последовательностей наследуются  от trait-а scala.Seq; и все они определяют наборы методов, упрощающих распространенные задачи. Например, метод map применяет  указанную функцию единообразно ко всем элементам последовательности, выдавая последовательность результатов  функции. Другой пример – метод filter, применяющий заданную функцию-предикат ко всем элементам последовательности, и возвращающий последовательность элементов, для которых предикат верен.

Применение этих двух функций  иллюстрирует следующая функция, sqrts, принимающая список чисел с двойной  точностью и возвращающая список, состоящий из квадратных корней всех неотрицательных элементов xs.def sqrts(xs: List[double]) : List[double] =

  xs filter (0<=) map Math.sqrt;

Заметьте, что Math.sqrt происходит из Java-класса. Такие методы могут  быть переданы функциям высшего порядка  так же, как методы, определенные в Scala.

For Comprehensions

Scala предоставляет специальный  синтаксис для более естественного  выражения комбинаций некоторых  высокоуровневых функций. For comprehensions – это обобщение list comprehensions, встречающихся в языках наподобие Haskell. С помощью for comprehension функция sqrts может быть записана так:def sqrts(xs: List[double]) : List[double]=

for(val x <- xs; 0 <= x)

  yield Math.sqrt;

Здесь "val x <- xs" – это  генератор, который производит последовательность значений, а "0 <= x" – фильтр, исключающий  из рассмотрения некоторые из получаемых значений. Comprehension возвращает другую последовательность, образованную значениями, выданными  частью yield. Comprehension может содержать  несколько генераторов и фильтров.

For comprehensions выражаются через  комбинации методов высшего порядка  map, flatMap и filter. Например, формулировка  приведенного выше метода sqrts будет  отображена на реализацию sqrts из  раздела "Последовательности".

Сила for comprehensions в том, что  они не привязаны к конкретному  типу данных. Они могут быть сконструированы  над любым несущим типом, который  определяет подходящие методы map, flatMap и filter. В это число входят все  типы последовательностей, необязательные значения, интерфейсы баз данных, а  также несколько других типов. Пользователи Scala могут применять forcomprehensions к собственным  типам, если они поддерживают нужные методы.

Циклы for в Scala похожи на comprehensions. Они отображаются на комбинации методов foreach и filter. Например, цикл for:for (val arg <- args)...

из листинга 1 отображается на:args foreach (arg => ...)

Абстракции

Важная проблема компонентных систем – как абстрагироваться от требуемых компонентов. В языках программирования есть два основных способа абстрагирования: параметризация и абстрактные члены. Первый способ, как правило, применяется для  функций, а второй, как правило, для  объектов. Java традиционно поддерживала функциональную абстракцию для значений и объектно-ориентированную абстракцию для операций. Новая Ява 1.5 (включающая generic-классы) поддерживает функциональную абстракцию также для типов.

Scala поддерживает оба стиля  абстракций и для типов, и  для значений. И типы, и значения  могут быть как параметрами,  так и абстрактными членами.  В этом разделе рассматриваются  оба стиля, а также большая  часть системы типов 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);

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