Синтаксис и семантика
В синтаксисе чистых объектно-ориентированных языков все может быть записано в форме посылки сообщений объектам. Класс в объектно-ориентированных языках описывает структуру и функционирование множества объектов с подобными характеристиками, атрибутами и поведением. Объект принадлежит к некоторому классу и обладает своим собственным внутренним состоянием. Методы — функциональные свойства, которые можно активизировать.
В объектно-ориентированном программировании определяют три основных свойства:
Инкапсуляция. Это сокрытие информации и комбинирование данных и функций (методов) внутри объекта.
Наследование. Построение иерархии порожденных объектов с возможностью для каждого такого объекта-наследника доступа к коду и данным всех порождающих объектов-предков. Построение иерархий является достаточно сложным делом, так как при этом приходится выполнять классифицирование.
Большинство окружающих нас объектов относится к категориям, рассмотренным в книге [Шлеер, Меллор 1993]:
· Реальные объекты – абстракции предметов, существующих в физическом мире;
· Роли – абстракции цели или назначения человека, части оборудования или организации;
· Инциденты – абстракции чего-то произошедшего или случившегося;
· Взаимодействия – объекты, получающиеся из отношения между другими объектами.
Полиморфизм (полиморфизм включения) — присваивание действию одного имени, которое затем разделяется вверх и вниз по иерархии объектов, причем каждый объект иерархии выполняет это действие способом, подходящим именно ему.
У каждого объекта есть ссылка на класс, к которому он относится. При приеме сообщения объект обращается к классу для обработки данного сообщения. Сообщение может быть передано вверх по иерархии наследования, если сам класс не располагает методом для его обработки. Если обработчик событий для сообщения выбирается динамически, то методы, реализующие обработчиков событий, принято называть виртуальными.
Естественным средством структурирования в данной методологии являются классы. Классы определяют, какие поля и методы экземпляра доступны извне, как обрабатывать отдельные сообщения и т. п. В чистых объектно-ориентированных языках извне доступны только методы, а доступ к данным объекта возможен только через его методы.
Взаимодействие задач в данной методологии осуществляется при помощи обмена сообщениями между объектами, реализующими данные задачи.
Пример описания в некотором абстрактном Pascal-подобном объектно
-ориентированном языке класса «точка», являющегося наследником класса «координаты» может выглядеть так:
Type TCoordinates = class (TObject)
x, y : integer;
Constructor Init (_x, _y : integer);
Function GetX : integer;
Function GetY : integer;
Procedure SetX (_x : integer);
Procedure SetY (_y : integer);
Procedure Move (dx, dy : integer);
Destructor Done; virtual;
Constructor Init();
x := _x; y := _y
end;
Function GetX : integer;
begin
GetX := x
end;
. . . . . . . . . . . . .
End;
TPoint = class (TCoordinates)
Color : integer;
Constructor Init (_x, _y, _Color : integer);
Function SetColor (_Color : integer);
Function GetColor : integer;
Constructor Init(_x, _y, _Color : integer);
Inherited Init(_x, _y);
Color := _Color
end;
. . . . . . . . . . . . .
End;
Если мы в дальнейшем хотим использовать экземпляры класса TPoint, их необходимо будет создать, вызвав метод-конструктор.
Var P1 : Point;
P1.Init (0,0, 14); P1.Move (+2, -2);
Для поддержки концепции ООР были разработаны специальные объектно-ориентирован-ные языки программирования. Все языки OOP можно разделить на три группы.
Чистые языки, в наиболее классическом виде поддерживающие объектно-ориентированную методологию. Такие языки содержат небольшую языковую часть и существенную библиотеку, а также набор средств поддержки времени выполнения.
Гибридные языки, которые появились в результате внедрения объектно-ориентированных конструкций в популярные императивные языки программирования.
Урезанные языки, которые появились в результате удаления из гибридных языков наиболее опасных и ненужных с позиций ООП конструкций.
4. Логическое программирование.
В 70-х годах возникла ветвь языков декларативного программирования, связанная с проектами в области искусственного интеллекта, а именно языки логического программирования.
Согласно логическому подходу к программированию, программа представляет собой совокупность правил или логических высказываний. Кроме того, в программе допустимы логические причинно-следственные связи, в частности, на основе операции импликации.
Таким образом, языки логического программирования базируются на классической логике и применимы для систем логического вывода, в частности, для так называемых экспертных систем. На языках логического программирования естественно формализуется логика поведения, и они применимы для описаний правил принятия решений, например, в системах, ориентированных на поддержку бизнеса.
Важным преимуществом такого подхода является достаточно высокий уровень машинной независимости, а также возможность откатов – возвращения к предыдущей подцели при отрицательном результате анализа одного из вариантов в процессе поиска решения (скажем, очередного хода при игре в шахматы), что избавляет от необходимости поиска решения путем полного перебора вариантов и увеличивает эффективность реализации.
Одним из недостатков логического подхода в концептуальном плане является специфичность класса решаемых задач.
Другой недостаток практического характера состоит в сложности эффективной реализации для принятия решений в реальном времени, скажем, для систем жизнеобеспечения.
Нелинейность структуры программы является особенностью декларативного подхода и, строго говоря, представляет собой оригинальную особенность, а не объективный недостаток.
в качестве примеров языков логического программирования можно привести Prolog (название возникло от слов PROgramming in LOGic) и Mercury.
5. Функциональное программирование.
Функциональный подход к программированию появился в результате проведения фундаментальных математических исследований.
Время появления теоретических работ, обосновывающих функциональный подход, относится к 20-м – 30-м годам XX столетия. Как мы убедимся впоследствии, теория часто значительно опережает практику программирования, и важнейшие работы, которые сформировали математическую основу подхода, были написаны задолго до появления компьютеров и языков программирования, которые потенциально могли бы реализовать эту теорию.
Что касается первой реализации, то она появилась в 50-х годах XX столетия в форме языка LISP, о котором речь пойдет далее.
Важнейшей характеристикой функционального подхода является то обстоятельство, что всякая программа, разработанная на языке функционального программирования, может рассматриваться как функция, аргументы которой, возможно, также являются функциями.
Функциональный подход породил целое семейство языков, родоначальником которых, как уже отмечалось, стал язык программирования LISP. Позднее, в 70-х годах, был разработан первоначальный вариант языка ML, который впоследствии развился, в частности, в SML, а также ряд других языков. Из них, пожалуй, самым «молодым» является созданный уже совсем недавно, в 90-х годах, язык Haskell.
Важным преимуществом реализации языков функционального программирования является автоматизированное динамическое распределение памяти компьютера для хранения данных. При этом программист избавляется от необходимости контролировать данные, а если потребуется, может запустить функцию «сборки мусора» – очистки памяти от тех данных, которые больше не понадобятся программе.
Сложные программы при функциональном подходе строятся посредством агрегирования функций. При этом текст программы представляет собой функцию, некоторые аргументы которой можно также рассматривать как функции. Таким образом, повторное использование кода сводится к вызову ранее описанной функции, структура которой, в отличие от процедуры императивного языка, математически прозрачна.
Поскольку функция является естественным формализмом для языков функционального программирования, реализация различных аспектов программирования, связанных с функциями, существенно упрощается. Интуитивно прозрачным становится написание рекурсивных функций, т.е. функций, вызывающих самих себя в качестве аргумента. Естественной становится и реализация обработки рекурсивных структур данных.
Благодаря реализации механизма сопоставления с образцом, такие языки функционального программирования как ML и Haskell хорошо использовать для символьной обработки.
Естественно, языки функционального программирования не лишены и некоторых недостатков.
Часто к ним относят нелинейную структуру программы и относительно невысокую эффективность реализации. Однако первый недостаток достаточно субъективен, а второй успешно преодолен современными реализациями, в частности, рядом последних трансляторов языка SML, включая и компилятор для среды Microsoft .NET.
Для профессиональной разработки программного обеспечения на языках функционального программирования необходимо глубоко понимать природу функции.
Заметим, что под термином «функция» в математической формализации и программной реализации имеются в виду различные понятия.
Так, математической функцией f с областью определения A и областью значений B называется множество упорядоченных пар
(a,b) A ? B,
таких, что если
(a,b1) f и (a,b2) f,
то
b1 = b2.
В свою очередь, функцией в языке программирования называется конструкция этого языка, описывающая правила преобразования аргумента (так называемого фактического параметра) в результат.
Для формализации понятия «функция» была построена математическая теория, известная под названием ламбда-исчисления. Более точно это исчисление следует именовать исчислением ламбда-конверсий.
Под конверсией понимается преобразование объектов исчисления (а в программировании – функций и данных) из одной формы в другую. Исходной задачей в математике было стремление к упрощению формы выражений. В программировании именно эта задача не является столь существенной, хотя, как мы увидим в дальнейшем, использование ламбда-исчисления как исходной формализации может способствовать упрощению вида программы, т.е. вести к оптимизации программного кода.
Кроме того, конверсии обеспечивают переход к вновь введенным обозначениям и, таким образом, позволяют представлять предметную область в более компактном либо более детальном виде, или, говоря математическим языком, изменять уровень абстракции по отношению к предметной области. Эту возможность широко используют также языки объектно-ориентированного и структурно-модульного программирования в иерархии объектов, фрагментов программ и структур данных. На этом же принципе основано взаимодействие компонентов приложения в .NET. Именно в этом смысле переход к новым обозначениям является одним из важнейших элементов программирования в целом, и именно ламбда-исчисление (в отличие от многих других разделов математики) представляет собой адекватный способ формализации переобозначений.
Систематизируем эволюцию теорий, лежащих в основе современного подхода к ламбда-исчислению.
Рассмотрим эволюцию языков программирования, развивающихся в рамках функционального подхода.
Ранние языки функционального программирования, которые берут свое начало от классического языка LISP (LISt Processing), были предназначены, для обработки списков, т.е. символьной информации. При этом основными типами были атомарный элемент и список из атомарных элементов, а основной акцент делался на анализе содержимого списка.
Развитием ранних языков программирования стали языки функционального программирования с сильной типизацией, характерным примером здесь является классический ML, и его прямой потомок SML. В языках с сильной типизацией каждая конструкция (или выражение) должна иметь тип.
При этом в более поздних языках функционального программирования нет необходимости в явном приписывании типа, и типы изначально неопределенных выражений, как в SML, могут выводиться (до запуска программы), исходя из типов связанных с ними выражений.
Следующим шагом в развитии языков функционального программирования стала поддержка полиморфных функций, т.е. функций с параметрическими аргументами (аналогами математической функции с параметрами). В частности, полиморфизм поддерживается в языках SML, Miranda и Haskell.
На современном этапе развития возникли языки функционального программирования «нового поколения» со следующими расширенными возможностями: сопоставление с образцом (Scheme, SML, Miranda, Haskell), параметрический полиморфизм (SML) и так называемые «ленивые» (по мере необходимости) вычисления (Haskell, Miranda, SML).
Семейство языков функционального программирования довольно многочисленно. Об этом свидетельствует не столько значительный список языков, сколько тот факт, что многие языки дали начало целым направлениям в программировании. Напомним, что LISP дал начало целому семейству языков: Scheme, InterLisp, COMMON Lisp и др.
Не стал исключением и изучаемый нами язык программирования SML, который был создан в форме языка ML Р. Милнером (Robin Milner) в MIT (Massachusetts Institute of Technology) и первоначально предназначен для логических выводов, в частности, доказательства теорем. Язык отличается строгой типизацией, в нем отсутствует параметрический полиморфизм.
Развитием «классического» ML стали сразу три современных языка с практически одинаковыми возможностями (параметрический полиморфизм, сопоставление с образцом, «ленивые» вычисления). Это язык SML, разработанный в Великобритании и США, CaML, созданный группой французских ученых института INRIA, SML/NJ – диалект SML из New Jersey, а также российская разработка – mosml («московский» диалект ML).
Близость к математической формализации и изначальная функциональная ориентированность послужили причиной следующих преимуществ функционального подхода:
1. простота тестирования и верификации программного кода на основе возможности построения строгого математического доказательства корректности программ;
2. унификация представления программы и данных (данные могут быть инкапсулированы в программу как аргументы функций, означивание или вычисление значения функции может производиться по мере необходимости);
3. безопасная типизация: недопустимые операции с данными исключены;
4. динамическая типизация: возможно обнаружение ошибок типизации во время выполнения (отсутствие этого свойства в ранних языках функционального программирования может приводить к переполнению оперативной памяти компьютера);
5. независимость программной реализации от машинного представления данных и системной архитектуры программы (программист сосредоточен на деталях реализации, а не на особенностях машинного представления данных).
По сравнению с другими языками программирования, в том числе с ранними функциональными языками, SML обладает рядом несомненных достоинств. К ним, в первую очередь, относятся:
1. безопасность программного кода, т.е. гарантия отсутствия переполнения памяти (в случае корректно написанной программы) и, соответственно, защиты от потенциальной неустойчивости работы системы посредством искусственного создания переполнения (такие языки программирования как «классический» C и C++ потенциально небезопасны);
2. статическая типизация: все ошибки несоответствия типов выявляются уже на стадии контроля соответствия типов в ходе трансляции (а не во время выполнения программы, как в LISP и Scheme);
3. выводимость типов (нет необходимости явно указывать тип каждого выражения, при этом результирующий программный код становится более удобочитаемым, его легче хранить и повторно использовать).
К числу других преимуществ языка функционального программирования SML следует отнести параметрический полиморфизм (возможность обрабатывать аргументы абстрактного типа). При этом трудозатраты на разработку программного обеспечения сокращаются за счет универсальности разрабатываемых функций (скажем, становится возможным написать унифицированную функцию для упорядочения по возрастанию элементов списка, которая сможет упорядочивать и список из целочисленных элементов, и список из символьных строк).
Еще одним мощным средством, облегчающим символьную обработку (в частности, декомпозицию и верификацию программ), является механизм сопоставления с образцом.
Построение программ из модулей способствует разделению интерфейсной (описательной) части и реализации (содержательной части) функций, что обеспечивает унификацию и сокращает время создания сложных программных проектов, облегчая тестирование на соответствие спецификациям заказчика.
Обработка исключительных ситуаций, которые описывают ход выполнения программы в случае возникновения тех или иных относительно редких событий, а также теоретически интересного механизма продолжений, создает возможность реализации программных систем, взаимодействующих с пользователем в реальном времени.
Заметим, что реализация преимуществ, которые предоставляют языки функционального программирования, существенно зависит от выбора программно-аппаратной платформы.
В случае выбора в качестве программной платформы технологии .NET, практически вне зависимости от аппаратной реализации, программист или руководитель программного проекта дополнительно получает следующие преимущества:
1. интеграция различных языков функционального программирования (при этом максимально используются преимущества каждого из языков, в частности, Scheme предоставляет механизм сопоставления с образцом, а SML – возможность вычисления по мере необходимости);
2. интеграция различных подходов к программированию на основе межъязыковой инфраструктуры Common Language Infrastructure, или CLI (в частности, возможно использование C# для обеспечения преимуществ объектно-ориентированного подхода и SML – функционального, как в настоящем курсе);
3. общая унифицированная система типизации Common Type System, CTS (единообразное и безопасное управление типами данных в программе);
4. многоступенчатая, гибкая система обеспечения безопасности программного кода (в частности, на основе механизма сборок).
2.2. Классификация языков программирования.
Существуют различные классификации языков программирования.
По наиболее распространенной классификации все языки программирования, в соответствии с тем, в каких терминах необходимо описать задачу, делят на языки низкого и высокого уровня.
Если язык близок к естественному языку программирования, то он называется языком высокого уровня, если ближе к машинным командам, – языком низкого уровня.
В группу языков низкого уровня входят машинные языки и языки символического кодирования: Автокод, Ассемблер. Операторы этого языка – это те же машинные команды, но записанные мнемоническими кодами, а в качестве операндов используются не конкретные адреса, а символические имена. Все языки низкого уровня ориентированы на определенный тип компьютера, т. е. являются машинно–зависимыми.
Машинно–ориентированные языки – это языки, наборы операторов и изобразительные средства которых существенно зависят от особенностей ЭВМ (внутреннего языка, структуры памяти и т.д.).
К языкам программирования высокого уровня относят Фортран (переводчик формул – был разработан в середине 50–х годов программистами фирмы IBM и в основном используется для программ, выполняющих естественно – научные и математические расчеты), Алгол, Кобол (коммерческий язык – используется, в первую очередь, для программирования экономических задач), Паскаль, Бейсик (был разработан профессорами Дармутского колледжа Джоном Кемени и Томасом Курцом.), Си (Деннис Ритч – 1972 году), Пролог (в основе языка лежит аппарат математической логики) и т.д.
Эти языки машинно–независимы, т.к. они ориентированы не на систему команд той или иной ЭВМ, а на систему операндов, характерных для записи определенного класса алгоритмов. Однако программы, написанные на языках высокого уровня, занимают больше памяти и медленнее выполняются, чем программы на машинных языках.
Программу, написанную на языке программирования высокого уровня, ЭВМ не понимает, поскольку ей доступен только машинный язык. Поэтому для перевода программы с языка программирования на язык машинных кодов используют специальные программы – трансляторы.
Существует три вида транслятора: интерпретаторы (это транслятор, который производит пооператорную обработку и выполнение исходного кода программы), компиляторы (преобразует всю программу в модуль на машинном языке, после чего программа записывается в память компьютера и лишь потом исполняется) и ассемблеры (переводят программу, записанную на языке ассемблера, в программу на машинном языке).
Языки программирования также можно разделять на поколения:
– языки первого поколения: машинно–ориентированные с ручным управлением памяти на компьютерах первого поколения.
– языки второго поколения: с мнемоническим представлением команд, так называемые автокоды.
– языки третьего поколения: общего назначения, используемые для создания прикладных программ любого типа. Например, Бейсик, Кобол, Си и Паскаль.
– языки четвертого поколения: усовершенствованные, разработанные для создания специальных прикладных программ, для управления базами данных.
– языки программирования пятого поколения: языки декларативные, объектно–ориентированные и визуальные. Например, Пролог, ЛИСП (используется для построения программ с использованием методов искусственного интеллекта), Си++, Visual Basic, Delphi.
Языки программирования также можно классифицировать на процедурные и непроцедурные.
В процедурных языках программа явно описывает действия, которые необходимо выполнить, а результат задается только способом получения его при помощи некоторой процедуры, которая представляет собой определенную последовательность действий.
Среди процедурных языков выделяют в свою очередь структурные и операционные языки. В структурных языках одним оператором записываются целые алгоритмические структуры: ветвления, циклы и т.д. В операционных языках для этого используются несколько операций. Широко распространены следующие структурные языки: Паскаль, Си, Ада, ПЛ/1. Среди операционных известны Фортран, Бейсик, Фокал.
Непроцедурное (декларативное) программирование появилось в начале 70-х годов 20 века, К непроцедурному программированию относятся функциональные и логические языки.
В функциональных языках программа описывает вычисление некоторой функции. Обычно эта функция задается как композиция других, более простых, те в свою очередь делятся на еще более простые задачи и т.д. Один из основных элементов функциональных языков – рекурсия. Оператора присваивания и циклов в классических функциональных языках нет.
В логических языках программа вообще не описывает действий. Она задает данные и соотношения между ними. После этого системе можно задавать вопросы. Машина перебирает известные и заданные в программе данные и находит ответ на вопрос. Порядок перебора не описывается в программе, а неявно задается самим языком. Классическим языком логического программирования считается Пролог. Программа на Прологе содержит, набор предикатов–утверждений, которые образуют проблемно–ориентированную базу данных и правила, имеющие вид условий.
Можно выделить еще один класс языков программирования – объектно–ориентированные языки высокого уровня. На таких языках не описывают подробной последовательности действий для решения задачи, хотя они содержат элементы процедурного программирования. Объектно–ориентированные языки, благодаря богатому пользовательскому интерфейсу, предлагают человеку решить задачу в удобной для него форме.
Первый объектно-ориентированный язык программирования Simula был создан в 1960-х годах Нигаардом и Далом.
Ява – язык для программирования Internet, позволяющий создавать безопасные, переносимые, надежные, объектно–ориентированные интерактивные программы. Язык Ява жестко связан с Internet, потому, что первой серьезной программой, написанной на этом языке, был браузер Всемирной паутины.
В последнее время, говоря о программировании в Internet, часто имеют в виду создание публикаций с использованием языка разметки гипертекстовых документов HTML. Применение специальных средств (HTML–редакторов) позволяет не только создавать отдельные динамически изменяющиеся интерактивные HTML–документы, используя при этом данные мультимедиа, но и редактировать целые сайты.
2.3. Язык высокого уровня Паскаль
Система PascalABC.NET
PascalABC.NET – это язык Delphi (Object Pascal) для платформы Microsoft .NET, содержащий все основные элементы современных языков программирования: модули, классы, перегрузку операций, интерфейсы, исключения, обобщенные классы, сборку мусора, а также некоторые средства параллельности, в том числе директивы OpenMP. Система PascalABC.NET включает в себя также простую интегрированную среду, ориентированную на эффективное обучение программированию. О преимуществах PascalABC.NET для обучения можно прочитать здесь. Отличия PascalABC.NET от Delphi (Object Pascal) приведены здесь. Примеры, иллюстрирующие основные особенности PascalABC.NET, находятся в меню "Помощь/Коротко о главном".
Язык Паскаль был разработан Никлаусом Виртом в 1970 г. как язык со строгой типизацией и интуитивно понятным синтаксисом. В 80-е годы наиболее известной реализацией стал компилятор Turbo Pascal фирмы Borland, в 90-е ему на смену пришла среда программирования Delphi, которая стала одной из лучших сред для быстрого создания приложений под Windows. Delphi ввела в язык Паскаль ряд удачных объектно-ориентированных расширений, обновленный язык получил название Object Pascal. Из альтернативных реализаций Object Pascal следует отметить многоплатформенный open source компилятор Free Pascal.
Основная причина создания PascalABC.NET – необходимость в современной простой и мощной бесплатной среде для языка Object Pascal, опирающейся на передовую платформу программирования Microsoft.NET. Платформа .NET обеспечивает язык PascalABC.NET огромным количеством стандартных библиотек и позволяет легко сочетать его с другими .NET-языками: C#, Visual Basic.NET, управляемый C++, Delphi.NET, Delphi Prism и др. Платформа .NET предоставляет также такие языковые средства, как единый механизм обработки исключений, единый механизм управления памятью в виде сборки мусора, а также возможность свободного использования классов, наследования, полиморфизма и интерфейсов между модулями, написанными на разных .NET-языках. О том, что такое платформа Microsoft.NET, о ее преимуществах для программирования и для обучения можно прочитать здесь.
Язык PascalABC.NET близок к реализации Delphi (Object Pascal). В нем отсутствует ряд специфических языковых конструкций Delphi, таких как открытые массивы, записи с вариантами, Variant-тип и т.п., некоторые конструкции изменены (не требуется писать overload, иной синтаксис перегрузки операций, вместо процедурных переменных используются делегаты .NET, интерфейсы реализованы в стиле C# и т.д.). Кроме этого, добавлен ряд возможностей: можно описывать переменные внутри блока, методы в записях, подпрограммы с переменным числом параметров, имеются операции +=, -=, *=, /=, в секции uses можно подключать пространства имен .NET, методы можно описывать непосредственно в теле класса или записи, поля классов можно инициализировать при описании, множества set могут быть на базе произвольных типов, введены операторы foreach и lock, переменные циклов for и foreach можно описывать непосредственно в заголовке цикла, имеются обобщенные классы (generics) и др.
Наиболее близким по идеологии к PascalABC.NET является язык Object Pascal системы CodeGear Delphi Prism. Однако он сильно изменен в сторону .NET: нет глобальных описаний, все описания помещаются в класс, содержащий классовый метод Main, отсутствует ряд стандартных подпрограмм языка Паскаль. Кроме того, система Delphi Prism - платная (за исключением консольного компилятора) и не содержит собственной оболочки (встраивается в Visual Studio).
Интегрированная среда PascalABC.NETобеспечивает подсветку синтаксиса, подсказку по коду (подсказка по точке, подсказка параметров подпрограмм, всплывающая подсказка по коду), форматирование текста программы по запросу, переход к определению и реализации имени, элементы рефакторинга.
Внутренние представления PascalABC.NET позволяют создавать компиляторы других языков программирования и встраивать их в среду с помощью специальных плагинов.