Прочее - История языков программирования. Часть 3. - PRCY⮭net
Pascal

Судьба языка Pascal во многом являет собой полную противоположность рассмотренным выше. Мы уже видели примеры языков программирования, которые проектировались с целью ни много ни мало оттеснить конкурентов, завоевать лидирующие позиции в промышленности, стать универсальным средством решения любых задач на компьютере Большинству из них не суждено было реализовать своих притязаний, и ныне они почти забыты, возникая из небытия лишь для полноты исторических обзоров вроде данного.

Язык впервые увидел свет в 1972 году, хотя основные идеи, заложенные в него, сформировались значительно раньше, в середине 1960-х. Автор Pascal'я, профессор Цюрихского университета Никлаус Вирт, в начале своей работы был лишен подобных вселенских амбиций. Как он впоследствии писал в своих мемуарах, он обучал студентов программированию на языках FORTRAN и Algol, и оба эти языка казались ему неподходящими для целей обучения программированию. Поэтому он решил разработать новый язык, в большей степени пригодный для обучения. Однако получившийся в результате язык шагнул далеко за пределы учебных аудиторий. Он был реализован практически на всех компьютерах, от 8-разрядных на базе микропроцессоров Intel 8080 и Z80 до солидных мэйнфреймов IBM и CDC, а для популярнейших IBM PC-совместимых персональных компьютеров стал одним из основных средств разработки прикладных программ благодаря превосходным инструментальным системам фирмы Borland (ныне Inprise).

В немалой степени Pascal базируется на идеях Algol'а, позаимствовав у него основные структурные конструкции. Впрочем, это не слепое подражание предшественнику, а вполне самостоятельный язык, которому удалось избежать многих слабостей и ограничений Algol'а.

Можно сказать, что немалую лепту в появление языка на свет внесло упрямство ван Вейнгаардена, руководителя комитета по разработке Algol 68, благодаря которому Вирт был вынужден покинуть комитет и самостоятельно реализовать свои идеи.

Структура программы на Pascal, как и сам язык в целом, весьма проста и логична. Программа состоит из заголовка, разделов меток, констант, типов, переменных и процедур, за которыми следует тело программы.

Метки присутствуют в языке как дань оператору goto, отказаться от которого Вирт не решился (да и чего греха таить, попадаются иногда ситуации, когда без использования goto программа становится громоздкой и малопонятной).

Возможность использования констант позволяет создавать более читаемые и легкие для понимания программы. Должным образом подобранное имя константы может оказаться гораздо информативнее, чем ее значение, там и тут разбросанное в тексте программы, да и изменить это значение при необходимости не в пример легче.

Набор типов, доступных программисту, гораздо шире, чем в Algol'е. Помимо известных логического, целочисленного и вещественного типов, Pascal предоставляет символьный тип, а также принципиально новый тип множество. Кроме того, имеются средства, позволяющие строить новые типы как ограниченные диапазоны целочисленного, а также перечисления. Имеются также структурированные типы однородные (массивы) и неоднородные (записи), которые также образуют новые типы. При этом элементом массива или записи также может быть структурированный тип, что позволяет строить довольно изощренные структуры.

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

Процедуры и функции поддерживают передачу параметров как по значению, так и по ссылке, позволяя при необходимости использовать побочные эффекты. Они могут быть вложенными, что позволяет гибко управлять областью видимости каждой из них. Поскольку локальные переменные создаются в стеке, допустима рекурсия, как прямая, так и косвенная. В случае использования косвенной рекурсии может возникнуть противоречие, т.к. в языке строго действует правило: подпрограмма должна быть объявлена перед ее использованием. Разорвать этот круг можно посредством объявления подпрограммы с ключевым словом forward.

Набор операторов языка позволяет строить хорошо структурированные программы. Среди них есть условный оператор if then else , операторы цикла со счетчиком for to/downto do , а также циклы с предусловием while do и с постусловием repeat until Кроме того, как расширение условного оператора if добавлен оператор выбора case, позволяющий организовать переключение на одну из многочисленных альтернативных ветвей программы. Имеется также оператор присоединения with, на мой взгляд, очень удачная находка, которой почему-то лишены более поздние языки вроде C++, Java и C#.

Конечно же, не лишен язык и некоторых слабостей. Одним из наиболее серьезных недочетов является то, что размер массива задает его тип. В соответствии со строгой типизацией это приводит к тому, что, например, процедура, сортирующая массив из 10 элементов, откажется сортировать массив из 100 элементов, поскольку формально они относятся к разным типам, даже если их элементы одного типа. Предусмотреть же разновидности сортировки для массивов разных размеров можно, но это загромождает программу и делает ее более подверженной ошибкам.

Тем не менее, сильных сторон у языка оказалось гораздо больше. На фоне языков FORTRAN, PL/I и Algol 60 он явился большим шагом вперед. Довольно быстро последовали реализации для различных платформ, поскольку язык был достаточно прост. Язык быстро завоевал прочные позиции в компьютерном мире, потеснив основных конкурентов, а его автор приобрел мировую известность.

В качестве иллюстрации приведу программу распечатки десятичного представления отрицательных степеней двойки (заимствовано из Вирт Н. Алгоритмы + структуры данных = программы: Пер. С англ. М.: Мир, 1985):

���:
program power(output);

const
= 10;

type
digit = 0..9;

var
i, k, r: integer;
d: array [1..n] of digit;

begin
for k := 1 to n do
begin
write(.);
r := 0;
for i := 1 to k 1 do
begin
r := 10 * r + d[i];
d[i] := r div 2;
r := r 2 * d[i];
write(chr(d[i] + ord(0)))
end;
d[k] := 5;
writeln(5)
end
end.

MODULA-2

Появление языка Pascal дало разработчикам программного обеспечения строгий и выразительный инструмент для создания ясных и хорошо структурированных программ, - тот самый инструмент, который был столь необходим в условиях известного кризиса программного обеспечения, становившегося все острее в то время. Однако неверным было бы считать, что все трудности информатики остались позади.

Действительно, новый язык вполне адекватно поддерживал парадигму структурного программирования, которая позволяла справляться со сложными задачами путем декомпозиции их на более простые, легче решаемые. Декомпозиция является великолепным средством борьбы со сложностью, позволяя человеческому разуму с ограниченными возможностями охватывать как всю задачу в целом, так и каждую ее часть детально, переходя с одного уровня абстракции на другой. Однако у декомпозиции есть и другая сторона.

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

Разумеется, не следует думать, что структурное программирование возникло именно благодаря языку Pascal, скорее наоборот. Идеи структурного программирования витали в воздухе задолго до его появления. Да и проекты на FORTRAN и COBOL, разумеется, выполнялись не в одиночку; однако именно в Pascal'е идеи структурного программирования были воплощены Виртом в рафинированном виде.

Однако именно коллективная работа над проектом явилась наиболее слабой стороной Pascal'я. Язык, задуманный изначально как учебный, не содержал в себе никаких средств для разделения программы на независимые части модули с последующей сборкой модулей в единое целое. Для компиляции программы весь ее исходный текст должен быть собран в единое целое.

Такой подход имеет ряд недостатков. Пожалуй, главный из них огромная нагрузка на компилятор. Достаточно поменять одну строчку в процедуре, чтобы весь проект в тысячи или десятки тысяч строк пришлось перекомпилировать заново. Те, кому довелось работать с оборудованием 80-х годов выпуска, должны помнить, насколько это небыстрый процесс. Да и склейка большого файла из отдельных кусков тоже не слишком приятная задача.

Конечно же, различные реализации компиляторов по-разному пытались бороться с этой проблемой (впрочем, без особого успеха). Так, один из компиляторов для PDP-11 позаимствовал из языка C конструкцию #include, избавив программиста от сборки проекта в один исходный файл. Впрочем, эта полумера не избавляла от необходимости полной перекомпиляции при внесении малейшего изменения в программу.

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

Впрочем, независимая компиляция таит в себе большую опасность. Компилятор никоим образом не может проверить соответствие объявления процедуры и ее реализации, полагаясь лишь на внимательность программиста. Практика же показывает, что любая технология, полагающаяся на внимательность и ответственность человека, обречена на провал. Ошибки, связанные с неправильной передачей параметров между раздельно скомпилированными процедурами, не так легко обнаружить, поскольку система контроля типов здесь бессильна.

Выход из создавшейся ситуации предложил тот же Вирт, разработав следующий язык программирования MODULA-2. (Этому проекту предшествовал другой, экспериментальный язык MODULA, который, впрочем, не вышел за стены лаборатории Вирта и о котором подробно рассказывать я не вижу смысла).

Синтаксис MODULA-2 весьма напоминает Pascal, и понять программу на MODULA-2, зная Pascal, не составит никакого труда. Набор операторов практически не изменился, встроенные типы данных также не претерпели существенных изменений, равно как и основные структуры. Впрочем, разработчики получили небольшой подарок: поскольку многие высказывали претензии к операторным скобкам Pascal'я begin/end, весьма громоздким по сравнению с {/}, столь привычным пишущим на C, многие операторы (операторы цикла, условные операторы и др.) начинаются неявной открывающей скобкой, делая многочисленные beginы ненужными. Таким образом, многие операторы являются составными автоматически и требуют лишь явного закрытия посредством end. (Хотя такой подход может обескуражить знатоков Pascal, однако поклонники Visual Basic вряд ли будут этим удивлены).

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

В результате компиляции модуля образуются два файла: файл описания интерфейса и объектный файл.

Для того, чтобы воспользоваться модулем, необходимо его импортировать. При импорте модуля используется описание его интерфейса и тем самым гарантируется столь же строгий контроль типов, как и в пределах локального модуля. При этом отслеживается соответствие версии модулей. Например, если модуль A импортирует модуль B и при этом в модуль B были внесены изменения, повлекшие изменение интерфейса, модуль A также потребует перекомпиляции. Если же изменения коснулись лишь реализации, а интерфейс остался неизменным, перекомпиляция A не потребуется. Таким образом, MODULA-2 не полагается на внимательность и аккуратность программиста, а подвергает согласованность интерфейсов строгому контролю при каждой компиляции.

Из других особенностей языка я бы отметил некоторые низкоуровневые средства, напоминающие C, для доступа к регистрам ввода/вывода и обработки прерываний, позволяющие использовать MODULA-2 для управления объектами в реальном времени, а также поддержку распараллеливания вычислений на уровне стандартной библиотеки.

В заключение следует отметить, что по непонятным мне причинам язык не получил широкого распространения, несмотря на явные преимущества перед своим предшественником. Хотя Вирт доказал на деле гибкость и мощность нового языка, разработав на нем операционную систему Medos и все программное обеспечение для довольно интересной мини-ЭВМ Lilith, программисты не спешили оставить полюбившийся им Pascal и перейти на MODULA-2.

В заключение исходный текст программы для рисования кривой Серпински (заимствовано из Вирт Н. Программирование на языке Модула-2: Пер. с англ. М.: Мир, 1987):

���:
MODULE Serpinsky;
FROM Terminal IMPORT Read;
FROM LineDrawing IMPORT width, Height, Px, Py, clear, line;

CONST
SqrSize = 512;

VAR
i, h, x0, y0: CARDINAL;
ch: CHAR;

PROCEDURE A(k: CARDINAL);
BEGIN
IF k > 0 THEN
A(k-1);
line(7, h);
B(k-1);
line(0, 2*h);
D(k-1);
line(1, h);
A(k-1)
END
END A;

PROCEDURE B(k: CARDINAL);
BEGIN
IF k > 0 THEN
B(k-1);
line(5, h);
C(k-1);
line(6, 2*h);
A(k-1);
line(7, h);
B(k-1)
END
END B;

PROCEDURE C(k: CARDINAL);
BEGIN
IF k > 0 THEN
C(k-1);
line(3, h);
D(k-1);
line(4, 2*h);
B(k-1);
line(5, h);
C(k-1)
END
END C;

PROCEDURE D(k: CARDINAL);
BEGIN
IF > 0 THEN
D(k-1);
line(1, h);
A(k-1);
line(2, 2*h);
C(k-1);
line(3, h);
D(k-1)
END
END A;

BEGIN
clear;
i := 0;
h := SqrSize DIV 4;
x0 := CARDINAL(width) DIV 2;
y0 := CARDINAL(height) DIV 2 + h;
REPEAT
i := i + 1;
x0 := x0 h;
h := h DIV 2;
y0 := y0 + h;
Px := X0;
Py := Y0;
A(i);
line(7,h);
B(i);
line(5, h);
C(i);
line(3, h);
D(i);
line(1, h);
Read(ch)
UNTIL (i = 6) OR (ch = 33C);
clear
END Serpinsky.

Alf
Information
  • Posted on 31.01.2010 22:25
  • Просмотры: 1336