Майк Бедфорд раскрывает тайны PL/I - языка, который, как надеялись в IBM, заменит FORTRAN и COBOL.
Названия языков программирования варьируются от банальных до точно описательных, от совершенно бессмысленных до, как в случае с темой этого месяца, откровенно неточных. Речь идет о языке PL/I. Буква I - это римская цифра, обозначающая единицу, что объясняет, почему он иногда неправильно указывается как PL/1. Это расшифровывается как Programming Language One, но это, конечно, не так. На самом деле, два языка, которые мы рассматривали ранее в этой серии, предшествовали ему, как и некоторые другие.
Чтобы попасть в наш список классических языков, язык должен существовать, мягко говоря, долго, и PL/I, безусловно, соответствует этому критерию: он был выпущен в 1964 году. Он был разработан компанией IBM для использования на мэйнфреймах System/360 и впервые был применен в лаборатории Hursley Laboratories в Великобритании в рамках программы разработки 360.
Чтобы прояснить ситуацию, рассмотрим основные языки, продвигаемые ранее компанией IBM в начале 60-х годов. FORTRAN использовался для научных приложений, а COBOL - для бизнес-приложений. Будучи гораздо более простым, FORTRAN предлагал те же типы инструкций, предоставляемых современными обычными языками, но COBOL был совершенно другим. Поскольку он был ориентирован на работу с данными в коммерческих задачах, он стал первым языком, позволяющим определять иерархические структуры данных и впоследствии манипулировать ими. Целью создания PL/I было объединить возможности этих двух языков и тем самым создать решение, удовлетворяющее как научным или техническим, так и коммерческим потребностям.
Но он пошел дальше. В то время как практически все ранние языки не имели практически никаких возможностей для поддержки блочно-структурированного программирования, ALGOL - язык, о котором рассказывалось ранее и появившийся в 1960 году, - все же придерживался этой важной парадигмы программирования и тем самым проложил путь практически ко всем современным популярным языкам. ALGOL никогда не продвигался компанией IBM в значительной степени, но, несмотря на это, его блочно-структурированный подход также повлиял на разработку PL/I. Таким образом, PL/I можно считать прямым потомком FORTRAN, COBOL и ALGOL, хотя он и не смог заменить ни один из этих языков.
Возможно, PL/I и не пользовался большим успехом на Западе, но за "железным занавесом" в начале 70-х годов все было совсем иначе. Судя по всему, PL/I широко использовался в Советском Союзе и других странах, находившихся в сфере его влияния, где он значительно приблизился к тому, на что рассчитывала IBM, а именно к вытеснению FORTRAN и COBOL. Однако, как и о многом другом в бывшем СССР, точную информацию получить трудно.
Разработка языка общего назначения PL/I была в значительной степени связана с созданием системы System/360, которая задумывалась как компьютер общего назначения. Она заметно отличалась от предыдущих машин, которые были ориентированы либо на научных, либо на коммерческих пользователей. Учитывая, что советские ЭВМ серии ЕС ЭВМ были клонами мэйнфреймов System/360 - по некоторым данным, изготовленными методом обратной разработки, - вполне естественно, что PL/I стал ключевым элементом программы ЕС ЭВМ. И в имеющейся ограниченной документации действительно указано, что PL/I поставлялся с машинами ЕС ЭВМ.
Однако мы не знаем, была ли советская версия PL/I идентична западной, или это была национализированная версия, как в случае с некоторыми советскими версиями ALGOL. Или, иначе говоря, была бы вторая строка нашего второго примера программы изначально столь же бессмысленной для русскоязычных программистов, как для большинства из нас нечто, не слишком похожее на ПРОЦЕДУРЫ ВАРИАНТЫ (ОСНОВНЫЕ)?
Сделай сам
Вы наверняка захотите попробовать свои силы в создании и выполнении PL/I-кода, но возможности для этого не столь богаты, как для предыдущих классических языков. Мы не смогли найти никаких онлайн-ресурсов для работы с PL/I, поэтому нам остаются только компиляторы. К сожалению, единственное предложение с открытым исходным кодом не обновлялось уже довольно долгое время. Поэтому мы рекомендуем свободно распространяемую реализацию Iron Spring, хотя она и имеет закрытый исходный код.
Последнюю версию компилятора для Linux можно найти на сайте www.iron-spring.com, документация также поставляется в составе дистрибутива. Она включает в себя файлик readme_linux.html, в котором описано, как установить и запустить компилятор, а также prog_guide.html, в котором, среди прочего, подробно описаны различия - в основном в области ввода и вывода - между Iron Spring PL/I и версией, на которой он основан, а именно IBM PL/I for MVS и VM.
Полного справочного руководства по языку пока нет, но оригинальное руководство IBM можно найти по адресу. Iron Spring PL/I не распространяется в виде исполняемого файла, но его компиляция из предоставленных исходных текстов не вызывает затруднений при использовании прилагаемого makefile. Несколько примеров PL/I-программ также входят в дистрибутив, а на сайте Iron Spring можно найти еще несколько примеров, чтобы испытать компилятор на практике, прежде чем приступать к созданию собственного кода. Также некоторое количество PL/I-кода можно найти и на сайте Rosetta Code, но в качестве первого шага мы рекомендуем попробовать примеры Iron Spring. Они находятся в каталоге samples, и если из этой папки выполнить команду make -f Makefile.Linux
, то все примеры будут скомпилированы и слинкованы, после чего их можно будет выполнить из командной строки.
Большинство из нас видели "родословные", показывающие, как языки программирования влияли на своих предшественников и, в свою очередь, сами влияли на своих преемников. Однако редко на этих диаграммах можно увидеть, что современные языки находятся на прямой линии происхождения PL/I. На самом деле, кажется, что она была чем-то вроде тупика. Тем не менее, за короткий срок он породил несколько языков, большинство из которых имеют названия, начинающиеся с PL/, чтобы подчеркнуть их семейное сходство.
По правде говоря, многие из потомков PL/I были диалектами исходного языка, и здесь можно упомянуть PL/M, разработанный компанией Intel для использования в микропроцессорах. PL/S несколько отличался тем, что был более приспособлен для системного программирования и, являясь расширением PL/I, позволял включать ассемблерный код в строку.
PL/C, Programming Language Cornell, был разработан в Корнельском университете. Он создавался как образовательный язык, поэтому в начале 70-х годов он мог бы стать альтернативой BASIC, несмотря на то, что эти два языка сильно отличаются друг от друга. В действительности, несмотря на то, что PL/C называли диалектом, он мало чем отличался от PL/I. Однако новизна реализации заключалась в том, что студенты никогда не сталкивались с ошибками компиляции, что было важно в условиях, когда на получение листинга после компиляции могли уйти часы. Для достижения этой цели система пыталась исправить синтаксические ошибки, а если это не удавалось, то преобразовывала ошибочные утверждения в вывод, чтобы помочь в отладке.
Если вы хотите попробовать отредактировать один из них в качестве упражнения, отредактируйте исходный код, удалите остальные файлы с тем же именем, но с разными расширениями, чаще всего .lst и .obj, и один без расширения, являющийся исполняемым, хотя это зависит от опций компилятора. Затем повторно выполните команду make
, которая теперь скомпилирует только ваш отредактированный код. А когда перейдете к написанию собственного PL/I-кода с нуля, то придется создавать собственные make-файлы - см. прилагаемые примеры.
Другой практический совет, который мы можем дать, касается подсветки синтаксиса. Поскольку вы не будете использовать IDE, единственным интерактивным инструментом для создания и последующего редактирования PL/I-кода будет текстовый редактор. Вы можете использовать любой из множества доступных FOSS-редакторов, но большинство из них не имеют подсветки синтаксиса PL/I, по крайней мере, в стандартной комплектации. Однако мы обнаружили, что для редактора Vim существует дополнение для подсветки синтаксиса PL/I, которое можно найти на сайте самого Vim'а. Настоятельно рекомендуем, тем более что PL/I, скорее всего, Вам незнаком. Вы можете увидеть ее в действии на нашем скриншоте, но также заметите, что в ней есть несколько причуд, поэтому не стоит доверять ей однозначно. Хотя мы еще не до конца разобрались с тем, какие цвета для чего используются, на скриншоте, демонстрирующем редактирование нашего второго примера программы, кажется, есть несколько несоответствий. Например, одно из имен переменных FIRST окрашено не так, как большинство других - белым, а не зеленым цветом. Мы предполагаем, что это следствие того, что мы случайно использовали ключевое слово в качестве имени переменной, что вполне законно, поскольку в PL/I нет зарезервированных слов, хотя это и не рекомендуется. Нам кажется, что если бы подсветка синтаксиса была более умной, например, учитывала бы контекст, то эти ложные примеры были бы окрашены в зеленый цвет как имена переменных.
Замена FORTRAN
Поскольку в одном из своих обличий PL/I рассматривался как потенциальная замена FORTRAN, мы начнем с рассмотрения простой программы для арифметических манипуляций. Код, представленный ниже, выводит на экран таблицу температур по Цельсию и их эквивалентов по Фаренгейту, причем температура по Цельсию начинается с 0°C и увеличивается с интервалом в один градус, пока мы не достигнем значения по Фаренгейту более 100°F.
CTOF:
PROCEDURE OPTIONS (MAIN);
DCL (F, C) FLOAT;
DO C = 0 to 100 UNTIL (F>100);
F = C * 1.8 + 32;
PUT SKIP EDIT (C, ‘C = ‘, F, ‘F’) (F(4,1), A, F(5,1), A);
END;
END CTOF;
Хотя мы представили его как приложение на языке FORTRAN, но намеренно выбрали для его реализации нечто, что явно не похоже на FORTRAN, а именно цикл DO ... UNTIL. В отличие от FORTRAN, который в своем первоначальном виде имел базовый цикл DO (например, DO 10 N = 1, 100
) в качестве единственной инструкции с блочной структурой, PL/I вдохновлялся ALGOL, предлагая несколько способов структурирования кода в блоки. Фактически, весь код определен как блок - в частности, основная процедура CTOF, заключенная в скобки CTOF:
и END CTOF
- и если бы это была более сложная программа, то было бы целесообразно разбить ее на другие процедуры, которые будут вызываться из основной.
Код должен быть в основном понятен, за исключением оператора PUT, который записывает вывод на консоль, которая в те времена, вероятно, представляла собой телетайпный терминал, но в нашем случае мы используем окно терминала. Содержимое первого набора круглых скобок в операторе PUT перечисляет элементы, которые должны быть выведены на экран, а именно смесь переменных C и F плюс несколько текстовых строк. А код во второй группе скобок определяет формат отображения, так, например, F(4,1)
определяет, что число должно быть четырехсимвольным с плавающей точкой (это число, включая десятичную точку), одна из цифр которого находится после десятичной точки.
Замена COBOL
Далее мы рассмотрим программу, выполняющую простую работу с файлами и манипулирование данными, которая является типичной для COBOL-приложения и представлена ниже:
DATAPROC:
PROCEDURE OPTIONS (MAIN);
DCL INFILE FILE RECORD ENV (F RECSIZE (28) TEXT);
DCL OUTFILE FILE RECORD ENV (F RECSIZE (28) TEXT);
DCL 1 DATA_READ,
2 NAME,
3 FIRST CHARACTER (10),
3 SECOND CHARACTER (10),
2 START_DATE,
3 DAY PICTURE ‘99’,
3 MONTH PICTURE ‘99’,
3 YEAR PICTURE ‘9999’;
DCL EOF FIXED (1);
OPEN FILE (INFILE) INPUT TITLE (‘employees.dat’);
OPEN FILE (OUTFILE) OUTPUT TITLE (‘recent-emoloyees.dat’);
EOF = 0;
ON ENDFILE (INFILE) EOF = 1;
DO WHILE (EOF = 0);
READ FILE (INFILE) INTO (DATA_READ);
IF YEAR > 2020 THEN DO;
/* PUT SKIP EDIT (NAME) (A); */
PUT SKIP EDIT (FIRST, SECOND) (2A);
WRITE FILE (OUTFILE) FROM (DATA_READ);
END;
END;
CLOSE FILE (INFILE);
CLOSE FILE (OUTFILE);
END DATAPROC;
Цель данного кода - чтение записей из файла, содержащего данные о сотрудниках компании, и запись в другой файл тех сотрудников, которые начали работать после 31 декабря 2020 года, а также вывод имен этих сотрудников в консоль. Данные, считываемые из файла, структурированы иерархически, причем полная запись (RECORD_READ) определяется в строке, начинающейся с DCL 1
. DCL
- это сокращение от DECLARE
, и при желании можно использовать DECLARE
полностью, а на скриншоте видно, что RECORD_READ - это переменная первого уровня. RECORD_READ разбивается на две переменные второго уровня - NAME и START_DATE; NAME разбивается на две переменные третьего уровня - FIRST и SECOND; а START_DATE также разбивается на переменные третьего уровня - DAY, MONTH и YEAR. Конечно, если бы это было реальное приложение, запись содержала бы гораздо больше информации о сотрудниках, но мы это опустили, чтобы не занимать место кодом, который не собираемся использовать. Тем не менее, этого достаточно, чтобы проиллюстрировать важную концепцию PL/I, унаследованную им от COBOL, а именно поддержку иерархических структур данных.
Несмотря на то, что мы описываем некоторые возможности PL/I как COBOL-подобные, между этими двумя языками есть одно очень заметное различие. Странная концепция COBOL, которой PL/I не подражал, заключалась в том, что высказывания должны выглядеть как предложения английского языка.
Такой иерархический подход позволяет манипулировать переменными любого уровня. Так, например, вся запись (RECORD_READ), находящаяся на первом уровне, считывается из файла и записывается в него за одну инструкцию, выборка производится на основе переменной третьего уровня YEAR, а имена записываются на консоль с помощью переменных третьего уровня FIRST и SECOND. На самом деле, поскольку это строго корректно, следовало бы написать утверждение, ссылающееся на FIRST и SECOND, а не на единственную переменную второго уровня NAME, как показано в закомментированном утверждении, заключенном в /* ... */
. Однако это не сработало, и компания Iron Spring подтвердила, что это глюк компилятора, который будет исправлен в следующем релизе, надеюсь, к тому времени, когда вы будете читать эту статью.
После объяснения концепции иерархических структур данных, хотя синтаксис, вероятно, будет отличаться от синтаксиса других языков, с которыми вы можете быть знакомы, мы подозреваем, что вам будет несложно понять, как это работает, поэтому мы не будем давать особых комментариев по этому поводу. Мы обойдем вниманием и выражения, начинающиеся с DCL INFILE
и DCL OUTFILE
- в конце концов, мы не ставим перед собой задачу превратить вас в полноценного программиста PL/I. А вот комментарий к оператору ON ENDFILE
будет уместен. Вы, наверное, догадываетесь, что оно делает, но, как сообщается, "PL/1 стал первым языком программирования, обеспечившим корректную обработку такой распространенной проблемы, как обнаружение конца последовательного файла при чтении".
Чтобы использовать этот код, необходимо создать ASCII-файл с именем employees.dat. Это должен быть 28-символьный файл фиксированной длины, причем каждый элемент данных в файле должен иметь фиксированную длину, как это определено в коде PL/I. Так, первая и вторая фамилии должны иметь пробелы, чтобы довести их до требуемых 10 символов, а все однозначные дни и месяцы должны иметь ведущий ноль. Вы можете создать другой файл, хотя он все равно должен называться employees.dat, но ниже приведен наш тестовый файл, и записи, которые код будет записывать в файл recent-employees.dat, будут FRED BEDFORD и HARRY HILL:
FRED BEDFORD 01012023
GEORGE SMITH 10102019
HARRY HILL 08082022
JENNY MARSHAL 05052018
Обратите внимание, что в последней строке файла не должно быть терминатора LF, иначе PL/I-программа будет считывать пустую строку в конце, что приведет к неожиданному поведению. Некоторые текстовые редакторы, включая Vim и GUI Text Editor, входящий в состав дистрибутива Ubuntu, добавляют LF в последнюю строку, но мы обнаружили, что Brackets этого не делает, поэтому лучше использовать его.
PL/I как системный язык
Менее известным, пожалуй, чем использование PL/I в качестве научного/технического/математического языка и для коммерческих, бизнес-ориентированных приложений, является то, что PL/I также был разработан для системного программирования. В этом смысле его можно сравнить с языком C и еще несколькими современными языками, поскольку он преодолевает разрыв между областями системного и прикладного программирования.
До появления PL/I ассемблер использовался в основном только для написания операционных систем и компиляторов. Отчасти это было связано с эффективностью, и мы признаем, что дизайн языка сам по себе не может решить эту проблему напрямую, поскольку эффективность в значительной степени связана с производительностью компилятора. Однако особенности языка также очень важны. В конце концов, если вы кодите на ассемблере, то можете делать практически все, что угодно, в то время как со многими ранними языками высокого уровня дело обстояло иначе. Не написав ни одного системного кода, автор не может сказать из первых рук, какие возможности необходимы системному программисту. Однако нам попалось следующее высказывание неназванного человека, участвовавшего в написании компиляторов PL/I, которое дает небольшое представление: "PL/1 был первым языком общего назначения с пригодным для использования строковым типом данных. В нем также есть битовые строки переменной длины". Нет необходимости говорить о том, что он также обеспечил средства работы с этими новыми типами данных, например, предоставил побитовые операторы OR, AND, XOR и NOT, чего не было в первоначальных версиях FORTRAN и COBOL. Точнее говоря, это можно сделать, конечно, на любом языке, поскольку все практические языки универсальны, но это эффективно только там, где побитовые операции предоставляются нативно.
Примечательно, что в некоторых версиях PL/I была реализована рекурсия, но не во всех более поздних версиях. Учитывая, что при каждом вызове рекурсивной функции значения всех ее локальных переменных помещались в стек, это быстро съедало ценный в те ранние времена ресурс - память.
Наверное, лучше всего о возможностях PL/I в области системного программирования можно судить на примере Multics, где он стал пионером в использовании языка высокого уровня для написания операционных систем. Multics была одной из первых многопользовательских операционных систем с разделением времени. Считается, что она оказала влияние на все современные операционные системы. Более того, утверждается, что ее наиболее значимым наследием для компьютерной индустрии стало создание UNIX, и мы все знаем, к чему это привело. По всей видимости, она имела и коммерческий успех, так как работала в общей сложности на 85 сайтах. Сегодня это не кажется большим количеством, но это было в эпоху, когда компьютеры были очень дорогими и потому мало распространенными. Проект Multics начался в 1964 году, а первый релиз был выпущен в 1969 году. Это был совместный проект Массачусетского технологического института, компаний General Electric (GE) и Bell Labs. Он был разработан на мейнфрейме GE-645, который был специально для него спроектирован. Сегодня General Electric почти не вспоминают как производителя компьютеров. Однако в 1970 году ее бизнес, включая Multics, был поглощен компанией Honeywell, которая впоследствии стала французской компьютерной компанией Bull, а в настоящее время входит в состав транснациональной IT-компании ATOS.
PL/I, конечно, не умер и не исчез - по некоторым данным, язык по-прежнему используется в новых разработках, в основном для системного программирования, в первую очередь на мэйнфреймах архитектуры IBM z/Architecture.