Язык Scala
Методы высшего порядка, применяемые в обработке последовательностей. Функциональная абстракция, трактовка параметров языка. Моделирование обобщенных (generic) типов с помощью абстрактных типов. Повторное использование классов. Автономные компоненты Scala.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курсовая работа |
Язык | русский |
Дата добавления | 24.03.2014 |
Размер файла | 45,6 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru
Размещено на http://www.allbest.ru
Оглавление
Введение
История
Истоки дизайна
Ключевые аспекты языка
Объектно-ориентированный язык
Функциональный язык
Java-подобный язык
Классы
Операции
Переменные и свойства
Операции - это объекты
Методы и функциональные значения
Функции - это объекты
Последовательности
For Comprehensions
Абстракции
Функциональная абстракци
Вариантность (Variance)
Абстрактные члены
Семейный полиморфизм (family polymorphism) и self-типы
Моделирование обобщенных (generic) типов с помощью абстрактных типов
Композиция
Повторное использование классов
Наследование
Trait
Декомпозиция
Объектно-ориентированная декомпозиция
Модель данных
Автономные компоненты
Адаптация компонентов
Виды (views)
Границы видов
Заключение
Список литературы
Введение
В настоящее время существует огромное количество языков программирования различных типов и уровней сложности. Один из них это язык объектно-ориентированного программирования Scala. С помощью языков программирования создаются компоненты программного обеспечения. В идеале, программное обеспечение должно собираться из библиотек предварительно написанных компонентов, так же, как аппаратура собирается из предварительно изготовленных чипов. На самом же деле большие куски приложений пишутся "с нуля", так что разработка ПО - это все еще больше ремесло, чем индустрия. Компоненты в этом смысле - простые части ПО, так или иначе используемые более крупными частями или целыми приложениями. Компоненты могут выглядеть по-разному. Это могут быть модули, классы, библиотеки, фреймворки, процессы или, например, Web-сервисы. Их размер может составлять от нескольких строк до нескольких тысяч. Они могут быть связаны с другими компонентами разнообразными механизмами - такими, как агрегирование, параметризация, наследование, удаленный вызов или передача сообщений.
Я считаю, что, по крайней мере, частично отсутствие прогресса в компонентном ПО объясняется недостатками языков программирования, используемых для определения и интеграции компонентов. Большинство существующих языков предлагает только ограниченную поддержку абстрагирования и композиции компонентов. Это относится, в частности, к таким статически типизированным языкам, как Java и C#, которые широко используются при создании компонентного ПО.
Язык Scala стал результатом исследований, направленных на разработку более хорошей языковой поддержки компонентного ПО. С помощью Scala я хотела бы проверить две гипотезы. Во-первых, я считаю, что язык программирования компонентного ПО должен быть масштабируемым в том смысле, что должна быть возможность с помощью одних и тех же концепций описать как маленькие, так и большие части. Поэтому сконцентрировалась на механизмах абстракции, композиции и декомпозиции вместо введения большого количества примитивов, которые могут быть полезными только на каком-то одном уровне масштабирования. Во-вторых, считаю, что масштабируемая поддержка компонентов может быть предоставлена языком программирования, унифицирующим и обобщающим объектно-ориентированное и функциональное программирование. Некоторые из основных технических новшеств Scala - это концепции, представляющие собой сплав этих парадигм программирования. В статически типизированных языках, к которым относится Scala, эти парадигмы до сих пор были почти полностью разделены.
История
Язык был создан в 2001--2004 годах в Лаборатории методов программирования EPFL. Scala была выпущена для общего пользования на платформе JVM в январе 2004 года и на платформе .NET в июне 2004 года. Планируется продолжить работу над формализацией ключевых аспектов языка и над разработкой оптимизаций, выполняемых компилятором.
Истоки дизайна
На дизайн Scala оказали влияние многие языки и исследовательские работы. Следующее перечисление включает часть работ.
Scala впитала значительное число концепций и синтаксических соглашений Java и C#. Способ выражения свойств во многом заимствован из Sather (англ.). Из Smalltalk взята концепция унифицированной объектной модели. Из BETA пришла идея, что всё, включая классы, должно допускать вложенность. Абстрактные типы в Scala очень похожи на абстрактные типы сигнатур в SML и OCaml, обобщённые в контексте полноценных компонентов. В некотором смысле Scala -- это продолжение работы Pizza (англ.). Как и Pizza, Scala компилируется под Java VM, добавляя функции высшего порядка, сопоставление с образцом, конструкции, которые исходно были созданы в сообществе функционального программирования. В то время как Pizza обратно совместима с Java, цель Scala -- всего лишь возможность взаимодействия, так что у неё больше степеней свободы в дизайне. Ещё одна цель Scala -- предоставить расширенные конструкции для абстракции и композиции компонентов -- общая с несколькими недавними исследовательскими разработками.
Ключевые аспекты языка
Scala-программы во многом похожи на Java-программы, и могут свободно взаимодействовать с Java-кодом.
Scala включает единообразную объектную модель -- в том смысле, что любое значение является объектом, а любая операция -- вызовом метода.
Scala -- это также функциональный язык в том смысле, что функции -- это полноправные значения.
В Scala включены мощные и единообразные концепции абстракций как для типов, так и для значений.
Она содержит гибкие симметричные конструкции примесей для композиции классов и trait-ов.
Она позволяет производить декомпозицию объектов путем сравнения с образцом.
Образцы и выражения были обобщены для поддержки естественной обработки XML-документов.
В целом, эти конструкции позволяют легко выражать самостоятельные компоненты, использующие библиотеки Scala, не пользуясь специальными языковыми конструкциями.
Листинг 1. Простая программа на Java и Scala.// Java
class PrintOptions
{
public static void main(String[] args)
{
System.out.println("Options selected:");
for (int i = 0; i < args.length; i++)
if (args[i].startsWith(""))
System.out.println(" " + args[i].substring(1));
}
}
// Scala
object PrintOptions
{
def main(args: Array[String]) : unit =
{
System.out.println("Options selected:");
for (val arg <- args)
if (arg.startsWith("-"))
System.out.println(" "+arg.substring(1));
}
}
Scala допускает внешние расширения компонентов с использованием видов (views).
На текущий момент Scala реализована на платформах Java и .NET.
Объектно-ориентированный язык
В Scala используется чистая объектно-ориентированная модель, похожая на применяемую в Smalltalk: каждое значение -- это объект, и каждая операция -- это отправка сообщения. Например, сложение x+y интерпретируется как x.+(y), то есть как вызов метода + с аргументом x в качестве объекта-приёмника и y в качестве аргумента метода. Рассмотрим другой пример: 1+2. Это выражение интерпретируется как (1).+(2).Обратите внимание, что скобки вокруг чисел обязательны, потому что лексический анализатор Scala разбивает выражение на лексемы по принципу самого длинного возможного сопоставления. Таким образом, выражение 1.+(2) разобьется на лексемы 1.,+ и 2, потому что лексема 1. длиннее лексемы 1 и первый аргумент сложения будет интерпретирован, как тип Double вместо Int.[3]
Функциональный язык
Каждая функция -- это значение. Язык предоставляет легковесный синтаксис для определения анонимных и карринговых функций. Каждая конструкция возвращает значение. Сопоставление с образцом естественно расширяется к обработке XML c помощью регулярных выражений.
Java-подобный язык
Scala рассчитана на взаимодействие с такими ведущими платформами, как Java или C#. Она разделяет с этими языками большинство основных операторов, типов данных и управляющих структур.
Для простоты в дальнейшем мы будем сравнивать Scala только с Java. Но, поскольку Java и C#, в свою очередь, имеют много общего, сходство с Java распространяется и на C#. В чем-то Scala даже ближе к C#, чем к Java. Например, в трактовке обобщенности (genericity).
В листинге 1 приведен пример простой программы на Java и Scala. Программа распечатывает все опции, введенные в командной строке. Примеры очень похожи. Оба языка используют один и тот же класс-примитив String, вызывающий одни и те же методы. Они также используют одинаковые операторы и одинаковые условные управляющие конструкции. Пример показывает и некоторые различия между языками:
В Scala кроме определений классов есть определения объектов (начинающиеся с object). Определения объектов определяют класс с одним экземпляром - который иногда называют singleton-объектом. В приведенном примере singleton-объект PrintOptions содержит функцию-член main. Singleton-объекты в Scala заменяют статические части Java-классов.
Scala использует для определений и параметров синтаксис id:type, тогда как Java использует префиксную запись для типов, т.е. type id.
Синтаксис Scala более систематичен, чем Java - все определения начинаются с ключевого слова. В приведенном примере с def main начинается определение метода.
В Scala нет специального синтаксиса для типов массивов и доступа к ним. Массив с элементами типа T записывается как Array[T]. Здесь Array - это стандартный класс, а [T] - параметр типа. На самом деле, массивы в Scala наследуются от функций. Поэтому обращение к массиву выглядит как вызов функции: a(i), вместо a[i] в Java.
Возвращаемый main тип выглядит как unit вместо используемого в Java void. Это следствие того, что в Scala предложения (statement) и выражения (expression) - это одно и то же. Каждая функция возвращает значение. Если правая часть функции - это блок, то будет возвращено значение последнего выражения. Результат может иметь тривиальное значение {} типа unit. Знакомые управляющие конструкции, например, as if-then-else, также обобщаются до выражений.
В Scala используется большинство управляющих структур Java, но традиционное для Java выражение for в их число не входит. Вместо этого существует for-comprehension, который дает возможность прямого перебора элементов массива (или списка, или перечисления) без необходимости в индексации. В Java 1.5 тоже есть понятие "расширенного цикла for", похожее на for-comprehension Scala, но имеющее больше ограничений.
Несмотря на различия в синтаксисе, Scala-программы могут без проблем взаимодействовать с Java-программами. В приведенном примере, Scala-программа вызывает методы startsWith и substring класса String, определенного на Java. В ней также происходит обращение к статическому полю out Java-класса System и вызов его (перегруженного) метода println. Это возможно даже несмотря на то, что в Scala нет концепции статических членов класса. На самом деле каждый Java-класс представляется в Scala как две сущности - класс, содержащий все динамические члены, и singleton-объект, содержащий все статические члены. Таким образом, System.out в Scala доступен как член объекта System.
Хотя это и не показано в приведенном выше примере, классы и объекты в Scala могут наследовать от Java-классов и реализовать Java-интерфейсы. Это позволяет использовать Scala-код во фреймворке Java. Например, Scala-класс мог бы реализовывать интерфейс java.util.EventListener. Экземпляры этого класса затем можно оповещать о событиях, поступающих от Java-кода.
Классы
Каждый класс Scala унаследован от класса Scala.Any. Подклассы Any попадают в одну из двух категорий: классы-значения, наследуемые от scala.AnyVal, и ссылочные классы, наследуемые от scala.AnyRef. Любое имя примитивного Java-типа соответствует классу-значению, и отображается на него с помощью предварительно определенного псевдонима типа. В Java AnyRef отождествляется с корневым классом java.lang.Object. Экземпляр ссылочного класса обычно реализуется как указатель на объект, хранящийся в куче программы. Экземпляр класса-значения обычно представляется напрямую, без указателей-посредников. Иногда приходится конвертировать два представления, например, когда экземпляр класса-значения виден как экземпляр корневого класса Any. Эти boxing-преобразования (и обратные им) выполняются автоматически, без явного указания в коде.
Обратите внимание, что классы-значения являются плоскими (одноуровневыми); все классы-значения - это подтипы scala.AnyVal, но не подтипы друг друга. Вместо этого используются представления (т.е. стандартные приведения) элементов различных классов-значений. Здесь рассматривали альтернативу в виде создания подтипов классов-значений. Например, можно было бы сделать Int подтипом Float, вместо создания стандартного приведения от Int к Float. Праграммисты отказались от этого, поскольку хотели сохранить как инвариант то, что интерпретация значения подкласса как экземпляра его суперкласса не меняет представления значения. Кроме всего прочего, они хотят гарантировать, что для каждой пары типов S<:T и каждого экземпляра x типа S выполнено следующее равенство:x.asInstanceOf[T].asInstanceOf[S] = x
Внизу иерархии типов лежат два класса - scala.AllRef и scala.All. Тип AllRef - это подтип всех ссылочных типов; его единственный экземпляр - ссылка на null. Поскольку AllRef не является подтипом типов-значений, null не является членом любого такого типа. Например, невозможно присвоить null переменной типа int.
Тип All - это подтип любого другого типа; экземпляров этого типа не бывает. Несмотря на то, что тип All - пустой, он может быть полезен в качестве параметра типа. Например, в библиотеке Scala определено значение Nil типа List[All]. Поскольку списки в Scala ковариантны, это делает Nil экземпляром List[T] при любом типе элементов T.
Операция равенства значений (==) разрабатывалась так, чтобы быть прозрачной в отношении представления типов. Для типов-значений это обычное (числовое или булево) равенство. Для ссылочных типов == расценивается как псевдоним метода equals из java.lang.Object. Этот метод изначально определяет равенство как равенство ссылок и должен переопределяться в подклассах, чтобы реализовать естественное понятие равенства для этих подклассов. Например, boxed-версии типов-значений могли бы реализовать метод equals как сравнение boxed-значений. В Java, наоборот, == всегда означает равенство ссылок для ссылочных типов. Это реализуется несколько эффективнее, но создает проблемы связности, поскольку boxed-версии равных значений могут оказаться неравными (относительно ==).
В некоторых ситуациях нужно не определяемое пользователем, а ссылочное равенство. Примером может быть хеш-консолидация, где эффективность - это все. Для таких случаев класс AnyRef определяет дополнительный не переопределяемый метод eq, реализующий ссылочное равенство (т.е. он ведет себя как == в Java для ссылочных типов).
Операции
Еще один аспект унифицированной объектной модели Scala - каждая операция является отправкой сообщения, то есть, вызовом метода. Например, сложение x+y интерпретируется как x.+(y), т.е. как вызов метода + с x в качестве объекта-приемника и y в качестве аргумента метода. Эта идея, впервые реализованная в Smalltalk, адаптирована к более традиционному синтаксису Scala следующим образом. Во-первых, Scala рассматривает имена операторов как обычные идентификаторы. Точнее, идентификатор - это либо последовательность букв и цифр, начинающаяся с буквы, либо последовательность операторных символов. Таким образом, можно определять, например, методы с именами +, <= или :: Далее, Scala трактует идентификатор, находящийся между двумя выражениями, как вызов метода. Например, в листинге 1 можно было бы использовать синтаксис операторов (arg startsWith"-") как "синтаксический сахар" для более традиционного синтаксиса (arg.startsWith("-")).
В качестве примера объявления и применения операторов, определяемых пользователем, рассмотрим следующую реализацию класса 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);
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 введено по причинам типобезопасности. Поскольку при составлении mixin-классов в другой класс копируются только различия, могло бы случиться так, что некоторые привнесенные члены ссылались бы на унаследованные члены, отсутствующие в новом контексте, порождая исключение “метод не найден”.
Неоднозначности. В Scala каждый класс наследуется только от одного суперкласса, и получает члены класса от нескольких других классов в процессе составления mixin-класса. Представьте, например, следующий подкласс класса Buffer, который вводит метод sameElements, а также внутренне используемый метод forall.class ComparableBuffer[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 sameElements(b: IterableBuffer[T]) : Boolean =
forall(elem => b.exists(elem.equals));
}
Можно создать новый класс стека MyStack, который содержит функциональность, предоставляемую как IterableBuffer, так и ComparableBuffer, используя оба класса как mixin-ы.class MyStack[T] extends Stack[T]
with IterableBuffer[T]
with ComparableBuffer[T]; // Ошибка!
В Scala методы, определенные в mixin-ах, либо представляют новые методы, либо переопределяют соответствующие методы суперкласса. Как показано в предыдущем примере, может случиться так, что два mixin-а определят один и тот же метод. Классу MyStack неясно, какой из методов forall использовать. Такая неоднозначность порождает ошибку времени компиляции, которая должна быть явно устранена программистом. Возможное решение - ввести новый метод forall, который перенаправляет вызовы нужной реализации. Следующий код использует примитив super[C], позволяющий ссылаться на конкретные определения в mixin-классе C:class MyStack[T] extends Stack[T]
with IterableBuffer[T]
with ComparableBuffer[T]
{
override def forall(p: T => Boolean) =
super[IterableBuffer].forall(p);
}
Trait
Кроме неоднозначностей, есть еще одна серьезная проблема множественного наследования - дилемма бриллиантового наследования, возникающая, если класс является наследником двух классов, разделяющих один суперкласс. Без дальнейших ограничений эти суперклассы в данном сценарии будут унаследованы дважды. Это приведет к удвоению состояния, инкапсулированного в данных суперклассах, и выльется в серьезные проблемы с непротиворечивостью.
Чтобы избежать этого, Scala позволяет подмешивать класс в другой класс, только если он не использовался ранее в этом другом классе как суперкласс или mixin. К сожалению, это правило накладывает сильные ограничения, исключая множество случаев, когда двойное наследование от одного класса не является проблемой - в частности, для классов без инкапсулированного состояния (в эту категорию попадают интерфейсы Java). По этой причине в Scala введено понятие trait-ов. Trait-ы - это абстрактные классы, не инкапсулирующие состояния ни в виде определений переменных, ни в виде предоставления конструкторов с параметрами. Однако, в противоположность Java-интерфейсам, они могут реализовать конкретные методы.
Поскольку trait-ы не инкапсулируют состояния, двойное наследование от них в Scala разрешено. Таким образом, в иерархии суперклассов класса можно использовать один trait несколько раз.
Декомпозиция
Объектно-ориентированная декомпозиция
Программистам часто приходится иметь дело со структурированными данными. В объектно-ориентированном языке структурированные данные обычно реализуются как набор классов, представляющих различные структурные конструкции. При работе со структурированными данными программист может полагаться только на методы, предоставляемые этими классами.
Модель данных
Модель данных для XML в Scala - это неизменяемое представление упорядоченного неранжированного дерева. В таком дереве у каждого узла есть метка, последовательность дочерних узлов и ассоциативный список атрибутов и их значений. Все это описано в trait-е scala.xml.Node, который, кроме того, содержит эквиваленты XPath-операторов child и descendant-or-self, записываемые как \ и \\. Для элементов, текстовых узлов, комментариев, инструкций по обработке и ссылок на сущности существуют конкретные подклассы.
Автономные компоненты
Язык Scala как таковой не предоставляет никаких примитивов для параллельного программирования. Вместо этого ядро языка создавалось так, чтобы упростить создание библиотек, предоставляющих различные модели параллелизма, строящиеся поверх поточной модели языка-основы. В этом разделе будет продемонстрирована мощь Scala на примере реализации небольшой библиотеки для отказоустойчивых активных объектов, подобных акторам Erlang (Erlang - функциональный язык, предназначенный для конкурентного (многопоточного) программирования, разработанный фирмой Ericsson, и используемый в телекоммуникационных системах этой фирмы - прим.ред.).
Адаптация компонентов
Каждая компонентная система с мощными конструкциями абстракции и композиции сталкивается с проблемой, когда дело доходит до интеграции подсистем, разработанных различными командами в разное время. Проблема состоит в том, что интерфейс компонентов, разработанных той или иной группой, часто не подходит клиентам, намеренным использовать этот компонент. Рассмотрим, например, библиотеку с классом наподобие GenList, обсуждавшегося выше. Клиент этой библиотеки может захотеть рассматривать такие списки как множества (sets), поддерживающие операции включения членов или проверки вхождения элемента во множество. Однако поставщик класса не предполагал такого сценария использования, и, следовательно, не включил эти методы в интерфейс класса GenList.
язык scala абстракция класс
Виды (views)
Scala представляет новую концепцию решения проблемы внешней расширяемости - виды (views). Они позволяют расширять класс новыми членами и trait-ами. Виды в Scala переводят в объектно-ориентированное представление используемые в Haskell классы типов (type classes). В отличие от классов типов, область видимости видов можно контролировать, причем в разных частях программы могут сосуществовать параллельные виды.
Подобные документы
Інтегрована інформаційна система менеджменту фірми SAP R/3. Інформаційні потреби управлінського апарату підприємства. Характеристика системи Scala. Характеристика змін в системі управління в результаті впровадження інформаційної системи управління.
контрольная работа [163,0 K], добавлен 27.07.2009Общие и отличительные черты объектов, связь между ними. Принципы организации иерархии и понятия объектно-ориентированной парадигмы программирования. Анализ предметной области, определение абстрактных типов данных, проектирование классов и коллекций.
курсовая работа [2,1 M], добавлен 11.03.2016Синтаксис языка РНР, его переменные и чувствительность их имен к регистру. Гибкость в отношении типов переменных, преобразование типов. Набор основных типов данных при работе с переменными. Методы передача переменных скрипту. Операторы цикла и выбора.
дипломная работа [27,3 K], добавлен 15.04.2009Выбор режима резания при токарной обработке с использованием различных типов резцов и материала обрабатываемой детали. Математическая модель и необходимые для расчетов таблицы. Функциональная схема автоматизации процесса расчета режима резания.
дипломная работа [1,3 M], добавлен 29.07.2016UML как язык моделирования, используемый архитектором при разработке дизайна системы для создания описания основных, важных аспектов программного обеспечения. Диаграмма прецедентов (UseCase), классов, видов деятельности, компонентов, последовательностей.
отчет по практике [633,1 K], добавлен 22.07.2012Причины возникновения объектно-ориентированного программирования. Графическое представление классов; их отличия от других абстрактных типов данных. Типы абстракции, используемые при построении объекта. Сущность инкапсуляции, наследования и полиморфизма.
контрольная работа [222,1 K], добавлен 04.06.2014Унифицированный язык моделирования (UML) как стандартный инструмент для создания "чертежей" программного обеспечения. Визуализирование, специфицирование, конструирование и документирование артефактов программных систем. Правила языка, диаграммы классов.
курсовая работа [613,9 K], добавлен 24.11.2010Создание имитационной модели системы массового обслуживания с помощью языка имитационного моделирования GPSS/PC - моделирование обработки на участке 500 деталей. Определение загрузки второго станка на вторичной обработке и вероятности появления отходов.
курсовая работа [602,3 K], добавлен 30.11.2010Рассмотрение основных типов данных: значений и ссылок. Отражение объектно-ориентированной методологии одиночного наследования IL в иерархической структуре общей системы типов. Виды интрефейсов и делегатов. Встроенные типы данных в спецификации CTS.
курсовая работа [99,0 K], добавлен 09.08.2015Основные понятия и назначение языка программирования СИ. Скалярные типы данных. Арифметические, логические и битовые операции над переменными целочисленного, вещественного, символьного, перечислимого типов. Примеры программ, выполняющие операции над ними.
презентация [269,9 K], добавлен 26.07.2013