Pull to refresh

Comments 59

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

Надо отсканировать как-нибудь свои запасы бумажных документов... В частности, есть все тома по ПЛ/1 для ОС ЕС. Практической пользы 0, но исторический интерес представляют.

Но всё равно избыточность языка PL/I огромна. Одной универсальностью задач это не объяснишь.

Было бы очень интересно почитать Ваши сканы.

PL/1 казался большим и сложным лишь на фоне языков того времени. По современным меркам он - ну как С * 2, ничего особенного. На фоне монструозного С++ - вообще милый подросток.

C++ на самом деле имеет несложный синтаксис, просто его элементы комбинируются очень запутанным образом, который тяжело понять и запомнить.

К тому же C++ эволюционировал к современному состоянию медленно и очень постепенно.

А в PL/1 захотели всё и сразу, и надорвались

В чем заключался надрыв? Тяжело было сделать компилятор в ограничениях того времени - вот и все.

Синтаксис там контекстно-зависимый. К тому же одни и те же ключевые слова выполняют совершенно разные функции. А уж семантика...

UFO landed and left these words here

>> C++ на самом деле имеет несложный синтаксис

Ну как сказать... я к примеру так и не освоил std:: и boost::, это какие-то монстры, которые сейчас являются стандартом и присутствуют в исходниках практически любой программы. Я не представляю, как можно запомнить тысячи функций (кто-нибудь вообще их считал ?), возможно ли вообще писать программы без копипасты или если нет автоподстановки.

UFO landed and left these words here

Сомнительное достойнство.

Когда вы не можете построить чью-нибудь программу, потому что там в каком-нибудь третьестепенном не нужном вам модуле используется не та версия какой-нибудь ненужной библиотеки, то это было бы очень полезным свойством.

А так обычно, конечно, strict лучше использовать.

UFO landed and left these words here

Польза от того, что там какая-то библиотека какой-то версии всё равно подключится нивелируется тем, что оно сломается не в compile time, а в runtime в каком-нибудь не очень предсказуемом месте в очень неподходящий момент. Так что удобство действительно весьма сомнительное. Если программа не собирается потому что отсутствуют какие-то нужные ей версии библиотек - это не просто так, значит она с ними разрабатывалась и тестировалась.

Сомнительное достойнство.

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

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

Ну и в ассемблере для любой модели /360 есть такая команда TRT, практически поиск подстроки –то-есть фича автоисправления мелких ошибок делается минимальными телодвижениями и очень коротким машинным кодом (важно для того времени).

Вот тут немножко подробностей ещё https://dl.acm.org/doi/pdf/10.1145/361972.361992

Именно исключением повторов компиляции при мелких ошибках и объяснялась сия фича; кроме того, она не была "безмолвной": компилятор уведомлял о проблеме в любом случае. Но толком её развить не успели, так как стали доступны терминалы, что позволило резко ускорить разработку (не надо было утром сдавать колоду перфокарт, а вечером приходить за распечаткой).

Добавлю, что граница ошибок при трансляции была не очень-то строгой. Компилятор маркировал сообщения кодами I, W, E и S (0, 4, 8, 12), и синтаксически корректная и абсолютно аккуратно написанная программа на PL/I (F) давала где-то от 0 до 4. В компиляторе уровня O это дело нормализовали, и стало возможно систематически получать 0, используя разумный стиль программирования.

Эта фича вообще была не свойством языка, а свойством отладочного компилятора IBM - которые использовался именно так, как вы написали.

Вообще, любой айбиэмовский компилятор PL/I генерировал исполняемый код при уровне диагностики ниже S. То есть при всех ошибках, которые можно было хоть как-то проинтерпретировать или игнорировать.

Не поленился посмотреть дамп загрузочного диска MVT 6.1.9, и вот что написано в незабвенной процедуре PL1LFCLG:

//GO EXEC PGM=LOADER,REGION=96K,

// COND=(9,LT,PL1L)

следует пояснить, что в данном случае означает "нестрогая типизация".

Указатель в числовую переменную, например, в языке присваивать нельзя.

А выражение '2'+2 переведется в 4 для fixed, в 4e0 для float и в '4' для переменной типа строка. Т.е. всего лишь числа могут быть представлены в разных видах, (почему-то сейчас это называется разными типами) в том числе, в виде строк в кавычках. На мой взгляд, это достаточно строго.

Под нестрогой типизацией понимается неявное преобразование типов данных между собой, чего в PL/I очень много. Фактически в PL/I прикладные типы данных – это разнообразные числа, строки символов и битовые строки, которые неявно преобразуются между собой произвольным образом.

При этом числа – это не “вообще число”, как, например, в Питоне или Лиспе, а именно значения конкретных типов, которые просто неявно могут быть преобразованы между собой по формальным правилам языка. Например:

declare

(i init (2147483647), j init (1)) fixed bin (31);

put skip list (i+j);

– получим отрицательное число, так как выражение i+j имеет по правилам языка тип fixed bin (31), а не просто число, которое могло бы иметь и больший диапазон.

Значение выражения '2'+2 имеет в любой реализации языка PL/I конкретный тип. Но я думаю, что ни один человек здесь (в том числе и я) не назовёт его без справочника.

Hidden text

fixed decimal (16, 0)

(Строка символов по зависящему от реализации соглашению преобразуется перед сложением в fixed decimal (15, 0), число имет тип fixed decimal (1, 0), а операция сложения добавляет 1 разряд к максимальной разрядности операнда).

Что касается неявного преобразования указателя в число, то это невозможно в языке PL/I исключительно по той причине, что невозможно придумать осмысленную интерпретацию для этого действия (за исключением копирования внутреннего представления этих переменных, которое буквально так и записывается: unspec (i) = unspec (p); ). При этом сами указатели в PL/I не типизированы внутри себя, все имеют одинаковый тип POINTER.

Прость так присвоить указатель в числовую переменную можно только в бестиповых языках вроде Форта и ассемблера.

Цикл с управляющей переменной, такой, что сначала проверяется конечное значение, а затем переменная меняется:

do i = 1 upthru 32767;

end;

(заметим, что цикл do i = 1 to 32767 зациклится навсегда, так как в 16-разрядных числах нет значения, для которого выполнится i > 32767).

А разве целые были по умолчанию 16-битные? IBM/360 был же 32-битным компьютером, с 32-битыми регистрами.

Fixed binary в PL/I 16-битный со знаком.

Память экономили.

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

Тут в связи с этим ещё интереснее ситуация: если бы переменная была описана, как 32-разрядная, то инкремент максимального значения вызывал бы исключительную ситуацию FIXEDOVERFLOW (хотя и запрещённую по умолчанию), а инкремент числа 32767 для 16-разрядной переменной никакой ситуации не вызывал, так как регистр-то 32-разрядный.

Судя по всему, кодстайл для пл/1, если писать его на современный манер, был бы просто огромным :)

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

Для меня PL/1 это как раз СКАЗОЧНОЕ многообразие типов и вполне себе логичное преобразование между ними. Например в базированных структурах наложенных на телеметрический кадр можно было учесть хоть каждый бит отдельно по средствам битовых строк. А потом воспользоваться оператором присваивания [структура] B = [структура] A BY NAME (присвоение только тем полям имена которых совпадают), описав результирующие поля как приемлемые числовые для последующей обработки.

И многозадачность с асинхронным вводом - выводом часто упоминаемые последние годы как достижение вызывают улыбку и воспоминания времён, когда компьютеры были БОЛЬШИМИ.

Например в базированных структурах наложенных на телеметрический кадр

Еще забавнее — наложенных на внутренние структуры ОС. Это позволяло писать на ассемблере только маленькие куски, минимально необходимые. Типа обработчиков прерываний.

Язык интересный, и возможности языка (как минимум упомянутые в статье) в общем вполне логичны и могли бы быть востребованы. Да, синтаксис древний, и не чувствуется полного понимания идеальной структуры синтаксиса (но для этого нужно было накопить тот огромный опыт языкового дизайна, которого тогда просто не было). Хотя этого понияния и сейчас в большинстве языков не чувствуется, нагородят странного синтаксического сахара лишь бы не как у всех:(

Вот забавно. С PL/I никогда не сталкивался, но сейчас работаю с RPG - это тоже IBM - альтернатива и ровесник Кобола, язык для коммерческих вычислений, до сих пор активно развивается и используется в экосистеме IBM (на нем, по оценкам, пишется боле 80% кода на middleware платформе IBM i). Но не суть.... Суть в том, что знакомо:

Переменные описываются при помощи оператора DECLARE (DCL), содержащего список переменных с их атрибутами.

Точно также:

dcl-f - объявление файла
dcl-s - обявление переменной (stanalone)
dcl-c - объявление константы
dcl-ds - объявление структуры данных
dcl-pr - объявление прототипа процедуры
dcl-proc - объявление тела самой процедуры и внутри него
dcl-pi - объявление "интерфейса процедуры" - список параметров и возвращаемое значение

Цикл с перечислением:

do i = 1, 3, 7, 9;

Немножко иначе, но тоже есть

for-each i in %list(1: 3: 7: 9);

Строковые типы представлены символьными (CHARACTER) и битовыми (BIT) строками фиксированной и переменной (VARYING) длины.

битовых нет, есть char - просто строка символов заданной длины и varchar которая есть по сути структура из двухбайтового целого (по умолчанию, можно объявить 4-байтовым) содержащего длину и char для содержимого.

Также есть based как модификатор при объявлении переменной. Тут суть в том, что указатели не типизированы - это просто указатель. Чтобы пользоваться тем, на что он указывает, нужно объявить переменную нужного типа и связать ее с указателем:

dcl-s var char(10) based(ptr);

ну а дальше присваиваем ptr нужное значение и работаем с var как с обычной переменной

Отдельная память для var, естественно, не выделяется.

Циклы с пред- и пост-условиями:

do while (a != 0);

end;

do until (a = 0);

end;

С циклами попроще:

for - обычный цикл, указывается параметр, предел, шаг и веперд/назад
for-each - цикл для работы со списком значений или массивом
dow - цикл Do-While
dou - цикл Do-Until

Переменные могут объединяться в структуры. Вложенность структур задаётся не придуманными позже операторными скобками, а как в Коболе, числами (внешний уровень имеет номер 1, а дальше уровни нумеруются произвольно), например:

declare

1 struct unaligned,

10 i binary fixed (15, 0),

10 x decimal float (16);

Тут попроще и попрозрачнее. Но есть и "сахарок" - можно явно задать размер структуры, превышающий суммарный размер ее полей. И можно явно задавать смещение поля относительно начала структуры, причем, поля могут перекрываться:

dcl-ds myDS len(50);     // структура размером 50 байт
  fld1 char(20);         // обычное поле, первые 20 символов стркутуры
  fld2 char(20);         // обычное поле, вторые 20 символов структуры
  fld3 char(20) pos(10); // поле, начинающееся с 10-го символа т.е. перекрывающее последние 10 символов fld1 и первые 10 fld2
end-ds;

А потом воспользоваться оператором присваивания [структура] B = [структура] A BY NAME (присвоение только тем полям имена которых совпадают)

тоже есть, но чуть иначе:

eval-corr ds1 = ds2; // присваиваются только те поля, у которых одинаковые имена

В левой части оператора присваивания могут использоваться так называемые псевдопеременные, представляющие собой, как сейчас бы сказали любители C++, функции над lvalue:

substr (str, 1, 1) = '*';

тут вообще 1:1

substr (str: 1: 1) = '*'; // занести * в первую позицию str
val = substr (str: 1: 1); // присвоить var значение из первой позиции str

есть еще ряд функций, работающих "в две стороны" (могут быть как rvalue, так и lvalue)

Кроме do- и begin-блоков, составными операторами являются обычные IF-THEN-ELSE и SELECT

И опять

if (...);
  ...
elseif (...);
 ...
else;
 ...
endif;

select;
  when ...;
    ...
  when ...;
    ...
  other;
    ...
endsl;

Обычно в PL/I все параметры процедур передаются по ссылке. Чтобы защитить передаваемую переменную от возможной модификации, можно использовать синтаксический трюк, поставив её в скобки и превратив таким образом в выражение, переприсвоенное значение которого не используется. Например, вместо call (a); написать call ((a));

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

Аналогично. По умолчанию - по ссылке. Чтобы было иначе - используем модификаторы const (защита от изменений) или value

dcl-proc myProc;
  dcl-pi *n int(10); // возвращает 4-бйатовое целое
    par1 char(10) const;  // не изменяется внутри
    par2 int(10)  value;  // передаем по значению
    par3 char(50);        // передем по ссылке
  end-pi;

  ...
end-proc;

способны взаимодействовать с СУБД DB2 и использовать SQL

Да!

Есть как нативные средства работы с таблицами

setll/setgt - позиционирование курсора перед первой записью с равным или большим значением ключа или после последней записи с равным или меньшеим значением ключа
read/readp/reade/readpe - чтение следующей / предыдущей / следующий с заданным значеним ключа / предыдущей с заданным значением ключа записей
chain - чтение записи с заданным значением ключа
write/update/delete - добавление, изменение, удаление записей

Например, чтобы прочитать все записи с заданным значением ключа делаем приверно так:

setll (keyVal) file;
reade file;

dow not %eof(file);
  ...
  reade file;
enddo;

exec sql insert into table t values (:par1, :par2);

Точно также. exec sql а дальше уже что угодно. Можно объявить курсор для заданного SQL выражения, открыть его с параметрами (с использованием хост-переменных) и затем читать в цикле по одной (или блоком) записи в указанные хост-переменные.

Для компиляции программы, содержащей SQL используется специальная команда которая сначала запускает препроцессор, а потом уже компилирует результат его работы.

Правда, на платформе IBM i exec sql можно и в С/С++ программах использовать точно также :-)

В общем, тут чувствуется что те, кто разрабатывал RPG, как минимум, плотно общались с теми, кто разрабатывал PL/I. RPG, конечно, сильно проще (где-то на уровне Паскаля), но очень много "общих слов" и местами подходов с PL/I.

Спасибо, очень интересные наблюдения! Я слышал о языке RPG, но никогда его не изучал.

Насколько я помню, RPG был изначально придуман тоже для S/360, а потом уже был перенесён и расцвёл на AS/400. И для людей, которые его разрабатывали, основным рабочим языком до того, очевидно, был PL/I.

Спасибо, очень интересные наблюдения! Я слышал о языке RPG, но никогда его не изучал.

У нас он мало распространён т.к. существует преимущественно в экосистеме IBM (да, были попытки вынести его за пределы - тот же VisualRPG, например, но особым успехом не отличались.

Как уже сказал - это альтернатива коболу для коммерческих расчетов. Т.е. типы данных с фиксированной точкой, мощные средства работы со строками, датами, временем (и поддержкой большого количества форматов оных), работа с БД как напрямую, так и через встраиваемый SQL...

RPG был изначально придуман тоже для S/360, а потом уже был перенесён и расцвёл на AS/400.

Изначально RPG был вообще для табуляторов придуман. Строго говоря, он начинался как "генератор отчетов" - Report Programm Generator Первые версии вообще были ориентированы на писание программ на бланках со строгим позиционированием. Это старый, FIXED RPG. Выглядит ужасно и нечитаемо:

     C     #EVT_ADD      WHENEQ    pMod                                                       MAIN
     C     #EVT_CLO      IFEQ      ELCEVT                                                     MAIN
     ?*                                                                                       MAIN
     C                   EXSR      CloRcd                                                     MAIN
     ?*                                                                                       MAIN
     C     DSEPMS        IFNE      *BLANKS                                                    MAIN
     C                   GOTO      MAINXT                                                     MAIN
     C                   ENDIF                                                                MAIN
     ?*                                                                                       MAIN
     C                   EXSR      AddRcd                                                     MAIN
     ?*                                                                                       MAIN
     C     DSEPMS        IFNE      *BLANKS                                                    MAIN
     C                   GOTO      MAINXT                                                     MAIN
     C                   ENDIF                                                                MAIN
     ?*                                                                                       MAIN
     C                   ENDIF                                                                MAIN

такого вот типа код...

Сейчас от этого ушли уже - обычный процедурный язык

       //-----------------------------------------------------------------
       //
       //   Check opened accounts
       //
       //-----------------------------------------------------------------
       DCL-PROC HasOpnAcc;
           DCL-PI *N Ind;
               iCUS Char(6) Const;
           END-PI;

           DCL-F SC20LF Usage(*Input) Keyed Block(*yes) UsrOpn Static;

           DCL-DS DSSCPFRkL LikeRec(SCPFR:*Key)  ;
           DCL-DS DSSCPFRiL LikeRec(SCPFR:*Input);

           //  Process accounts
           if not %open(SC20LF);
             open SC20LF;
           endif;

           DSSCPFRkL.SCAN = iCUS;
           SetLL %KDS(DSSCPFRkL:1) SCPFR;
           Read SCPFR DSSCPFRiL;

           DoW (%EOF(SC20LF) and
                DSSCPFRiL.SCAN = DSSCPFRkL.SCAN);
               If ((DSSCPFRiL.SCNANC in %list('40817': '40820': '42301': '42601'))
                    and DSSCPFRiL.SCAI17 = 'N'
                    and DSSCPFRiL.SCAI30 = 'N'
                    and DSSCPFRiL.SCAI80 = 'N'
                  );
                   Return *On;
               EndIf;

               Read SCPFR DSSCPFRiL;
           EndDo;

           Return *Off;

       END-PROC;

Там, конечно, есть особенности. Например, ушли (совсем) от GOTO. Но при этом сохранились подпрограммы (subroutines) которые можно описывать в рамках процедур и использовать как средство дополнительного структурирования или переиспользования кода. Например, у нас идет обработка данных какого клиента. Есть три ветки - общая ля физиков и юриков, отдельная для физиков, от дельная для юриков. Пишем так:

    select;
      when Client.ClTp = 'F';
        exsr srCheckAll;
        exsr srCheckF;

      when Client.ClTp = 'U';
        exsr srCheckAll;
        exsr srCheckU;
      
    endsl;

Если тип клиента F (физлицо) - идем по общей ветке, потом по ветке обработке ФЛ, если U (юрлицо) - по по общей ветке, потом по ветке обработки ЮЛ. Т.е. общая канва алгоритма видна сразу, а за подробностями см. соотв. сабрутину.

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

Историю S/360 и дальше я, увы, не знаю. Насколько в курсе - это мейнфреймы, ныне платформа IBM z (z/OS)

IBM i - это middleware серверы. Там история шла S/32 - S/34 - S/36 - S/38 - AS/400. Нынче - IBM i (i/OS) Это все достаточно подробно описано в книге Френка Солтиса "Основы AS/400" (IBM i многие по прежнему по привычке называют "АС-ка").

Вроде бы, раньше там был PL/I, но когда ввели концепцию ILE (Integrated Language Environment) он туда уже не попал (сейчас туда входят CL - Command Language - язык системных команд, RPG, С/С++ - можно писать несколько модулей (*MODULE) на разных языках и объединять их (BIND) в один программный объект (*PGM) главное правильно прототипы описывать чтобы функцию на С, например, вызывать из процедуры на RPG).

В общем, тут можно много и долго расказывать :-) Вот тут немного было (хотя там есть ряд неточностей)

Узнаю банковскую специфику в коде.

Так естественно :-)

RPG - язык для коммерческих расчетов. Не универсальный. Тут вот сейчас юный падаван обучается - в учебной задачке у него было

Заполните массив из 100 элементов рандомными значениями от 0 до 50.

Приходит с вопросом

вопрос появился: мне нужно получить рандомное число, про что нужно почитать?

А в RPG нет генератора ПСЧ. Не завезли. Но... Поскольку работаем в ILE, то всегда можем дернуть любую функцию из библиотеки С. Достаточно правильно ее описать в виде прототипа внешней процедуры:

dcl-pr Random int(10) extproc('rand');
end-pr;

И все. Пишем

dcl-pr Random int(10) extproc('rand');
end-pr;

dcl-s arr int(10) dim(100);
dcl-s i   int(10);

for i = 1 to 100;
  arr(i) = %rem(Random(): 51);
endfor;

и оно будет заполнять массив ПСЧ, дергая для этого Сишную rand() (%rem - остаток от деления, вообще все BIFы - Built-In Functions - в RPG начинаются с %)

Сама платформа IBM i тоже специализированная. Да, дорого, да специфично. Но. Высокая производительность там, где одновременно работает множество параллельных заданий (одна из особенностей - там практически нулевые накладные расходы на переключение контекста задания на уровне ОС). И очень надежно.

Понятно что для бухгалтерии ООО "Сукин и Сын" это все мегаизбыточно во всех отношениях - там 1С под виндой за глаза. А вот для крупного банка, где время недоступности mission critical систем (а это фактически все ядро АБС) нормируется регулятором и исчисляется минутами, вполне себе оправдано.

И, кстати, прямой (не SQL) доступ к БД в данном случае работает быстрее чем SQL т.к. не требуется время и ресурсы на подготовку запроса, а нужно - то всего лишь пробежать по таблице по индексу и проверить есть ли там хотя бы одна запись с нужными значениями полей. Как нашли - выходим, дальше уже не интересно.

Где запрос посложнее, там уже SQL будет эффективнее. Например:

  SqlNMC = 'select NMCCUS,' 
         + '       NMCCLC,'
         + '       GFCPNC,'
         + '       GFCRF,'
         + '       BGDTBR,'
         + '       cast (case'
         + '               when NMCTP <> ''U''' 
         + '                 then ''F'''
         + '               else ''U'''
         + '             end as char(1)),'
         + '       cast (''3'' as char(1))'
         + 'from NMCPF '
         + 'join GFPF on (GFCUS, GFCLC) = (NMCCUS, NMCCLC) '
         + 'join BGPF on (BGCUS, BGCLC) = (NMCCUS, NMCCLC) '
         + 'where NMCDT >= ?'
         + '  and NMCTP <> ''B''';

  Exec Sql DECLARE SqlStatNMC STATEMENT;
  Exec Sql PREPARE SqlStatNMC FROM :SqlNMC;
  Exec Sql DECLARE ECLRCLNMCCur CURSOR FOR SqlStatNMC;

  Exec Sql OPEN ECLRCLNMCCur using :WDate;

  Exec Sql FETCH ECLRCLNMCCur for :SQL_ROWS rows INTO :DataBuf;

Ну и так далее...

Запрос готовится при компиляции программы, при исполнении используется готовый план выполнения запроса. Во всяком случае, в родной DB2 это так.

Нет. Можно посмотреть PEX'ом. План запроса реализуется в рантайме. И именно в DB2.


Поэтому там, где оно будет вызываться многократно в рамках одной АГ, всегда prepare выносим в блок инициализации при первом вызове. А потом только open/fetch/close
Иначе получаем:

Из PEX статистики работы ***** видно, что 33% времени и 36% ресурсов  CPU  тратится на выполнение QSQRPARS в программе ******, т.е. парсинг статических выражений при подготовке SQL запроса

QSQRPARS - это как раз те самые prepare - подготовка плана запроса.

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


Если избежать этого не удается, то единственный выход - руками формировать SQLDA (заполнять массив хост-переменных) и потом

Exec Sql OPEN Cur using descriptor :SQLDA;

Но в целом это очень плохая практика.

В цитируемой ситуации удалось перейти с SQL на нативный RPG (правда, там алгоритм хитрый получится) и в результате

Среднее время получения ответ, при запуске СМ:

Текущая версия функционала (копия в промсреды от ***) - 7.98 мсек

Обновленная версия, СМ -  6.71 мсек

Рессурсовзатратность уменьшилась до незначительных показателей. 

Чуть-чуть выиграли по времени и очень значительно по ресурсам. Причем, смотрю это старый код и вижу, что сейчас сделал бы чуть иначе и оно было бы еще быстрее при тех же или меньших ресурсозатратах (в частности, там надо бы включить режим блочного чтения - block(*yes) в объявлении файлов - это даст ускорение до 3-4 раз, ну и по мелочи еще кой-чего...).

Но там реально алгоритм замороченный был т.к. изначально было два запроса:

buf =
'SELECT DISTINCT ELWWRD ' +
'FROM ELWPF LEFT JOIN EWFPF ' +
'ON ELWWID=EWFID ' +
'WHERE EWFEWF IN (' + wrds + ') AND EWFTES=''N''';

и

buf =
'SELECT ELWEID, ELWSPE ' +
'FROM ELWPF WHERE ' +
'ELWWRD IN (' + wrds + ') ' +
'and ELWTES=''N'' ' +
'GROUP BY ELWEID, ELWSPE' +
' HAVING COUNT(*)=MAX(ELWCNT)';

где wrds формировался каждый раз заново на лету.

Особенно весело было реализовывать GROUP BY и HAVING. Но это уже совсем другая история :-)

Классическая DB2 (на мейнфреймах и рабочих станциях) работает со статическим SQL следующим образом:

1) Препроцессор SQL при компиляции исходного текста программы ищет в исходном коде фрагменты EXEC SQL и выделяет из них запросы SQL в специальный файл bnd.

2) Утилита db2bind, вызываемая обычно тоже в рабочем процессе компиляции программы, получает на вход файл bnd и "привязывает" его к базе данных. Фактически это означает, что запросы SQL из файла bnd преобразуются оптимизатором СУБД в план выполнения запросов, то есть непосредственно в описание низкоуровневых команд навигации по базе данных. Это описание записывается в пакет (PACKAGE) в базе данных, соответствующий исходному bnd-файлу и, таким образом, исходному тексту программы. Это и есть фаза PREPARE.

3) При выполнении программы она выполняет уже нее подготовленные для исполнения команды из своего пакета (фаза EXECUTE).

4) Если мы хотим связать программу с новой базой данных, или если содержимое базы данных сильно изменилось, так что у нас появились основания предполагать, что оптимизатор может построить другой план выполнения запросов, или если мы хотим дать программе новые права на доступ к базе данных, то мы можем снова руками вызвать утилиту db2bind и выполнить повторно фазу PREPARE, построив из файла bnd пакет заново.

Если же мы в статическом SQL формируем текст запроса "на лету" в рантайме, тогда в файле bnd ему будет соответствовать оператор SQL PREPARE, и фаза PREPARE будет фактически выполняться при исполнении программы, чего, конечно, желательно избегать. По сути это то же самое, что динамический SQL, только с лишним геморроем.

Построение плана запроса в compile time работает только для статического SQL. А его возможности несколько ограничены и их не всегда хватает - приходится использовать динамику.

В данном случае все упирается в параметр wrds - списко неких "ключевых слов", формирование которого происходит на основе входных параметров по хитрому алгоритму, который ну никак в SQL запрос не впихивается (фактически, это отдельная процедура этим занимается).

Я не к тому что SQL это плохо. Это хорошо. И пользуемся много и активно:

    exec sql declare curChkNamF cursor for 
      select ECF1LST, ECF1ID, ECF1FNM 
        from NMCPF
      join NMFCPF  on NMFCID = NMCID
      join NMFC1PF on NMFC1EID = NMFCEID
      join ECF1PF  on ECF1KEY = NMFC1ELM and ECF1ACT = 'Y'
      join ECFPF on (ECFLST, ECFID) = (ECF1LST, ECF1ID) and ECFACT = 'Y' and ECFDTBR = :DTBR
      where NMCCUS = :CUS and NMCCLC = :CLC;

    exec sql open curChkNamF;
    exec sql fetch curChkNamF for :sqlRows rows into :dsSIDNam;

Просто не всегда такое получается...

Кстати, спасибо за коммент - воспользовался случаем упорядочить свое понимание статического/динамического SQL. Теперь при ревью кода буду больше внимания обращать на то, чтобы динамикой не злоупотребляли там, где можно статикой обойтись.

Приведённая вами программа исполняется медленно потому, что в ней есть неэффективный оператор PREPARE. Но текст селекта можно было бы записать прямо в описание курсора:

EXEC SQL DECLARE CURSOR FOR SELECT ...

и тогда всё было бы эффективно.

Ну я написал выше - не всегда есть возможность остаться в рамках статики. К сожалению, конструкцию вида

ELWWRD IN (' + wrds + ')

где список wrds на каждом вызове меняется (как по количеству, так и по содержимому) запихнуть в статику не получилось.

Также бывают ситуации когда на входе 10-15 параметров из которых обязательных 2-3, остальные опциональны (могут передать, могут не передать) и тогда список условий в блоке where на каждом вызове варьируется - где-то 2-3 условия, где-то 10-15. И это тоже не статика - там план запроса может меняться в зависимости от того, что в where.

Более простые запросы - да, они легко параметризируются.

Фирма IBM была не заинтересована в безвозмездном распространении языка на рабочих станциях

Бизнес модель у IBM изначально рассчитана на распил огромных бюджетов уровня оборонки, госов, банков и т.п. Переусложненность и не стандартность решений это вообще-то их конкурентное преимущество. Они не стремятся в масмаркет, программы обучения инженеров даже начального уровня по стоимости там как у космонавтов. Безвозмездное распространение языка звучит как анекдот: прикольно, но зачем? - если правильнее добавить ещё строчку в бюджет. Мне кажется СССР пропустили именно этот момент, пытаясь скопировать и массово тиражировать технологию, которая для этого вообще не была предназначена.

В 21 веке IBM стала много делать для опенсоурса, но момент для продвижения собственно решений IBM был уже упущен.

Хотя таким недальновидным образом IBM своими руками маргинализировала несущие золотые яйца мейнфреймовские технологии, и теперь даже уже её собственное руководство не знает, зачем это нужно.

IBM это корпорация - в том смысле, что у них куча подразделений, занимающих разные ниши. Есть те кто опенсорсят в условные линуксы на сишечке. И в то же время есть те, кто продолжает пилить решения для энтерпрайзов на коболах, которые они ни когда не стремились и не стремятся продвигать как масмаркет - нельзя упустить то чего нет.

Людей-то новых откуда под это брать? В самой же IBM в итоге и ставят линуксы на мейнфреймы. А если нет разницы, зачем платить больше? ©

Людям, которые пилили PL/I в IBM, точно было абсолютно пофиг, купят юзеры компилятор за жалкие несколько килобаксов или получат его бесплатно. Как вы верно заметили, они на хлеб с икрой зарабатывают совсем другим. А пионеры в результате не узнали главную тайну буржуинства и остались пионерами.

Людей-то новых откуда под это брать?

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

Достоинства вещей являются продолжением их недостатков. PL/I это язык с объёмом знаний, который тянет на второе высшее образование. Это не может быть массовым. Для массового использования нужны простые вещи, как джаваскрипт. PL/I классно вписывается во вполне конкретную модель продаж, и непригоден для всего остального. Это не значит, что они упускают рынок. Просто для каждого рынка свой товар.

А если нет разницы, зачем платить больше?

Ну а как иначе распилить огромный бюджет, чтобы на следующий год его не порезали? Здесь нужен правильный партнёр.

Поделюсь некоторыми деталями появления PL/I на ПК.


В 1980 году нашлись люди, озаботившиеся переносом этого языка на мелкие компьютеры. Одним из первых был некий д-р Роберт Фрайбургхаус.
Он решил сначала немного урезать IBM-ский вариант языка и не тащить всякую экзотику вроде аглийской денежной системы в типах констант.
Фрайбургхаус собрал нечто вроде конференции по разработке подмножества языка и пригласил на нее Г. Килдэла. Килдэл имел за плечами опыт сопровождения транслятора PL/I в военно-морском институте. Но главное – он был специалистом №1 по микропроцессорам и разработчиком ОС. Язык PL/I ему сильно не нравился и он бы никуда не поехал, но по случайности конференция была в том же городе и на той же улице, где он жил. А кроме этого, Роберт обещал бесплатные обеды)) Когда Килдэл пришел, оказалось, что произошло недоразумение – Фрайбургхаус думал, что PL/M – это не полуассемблер, а прямо PL/I на микропроцессоре и пригласил Гарри как докладчика и консультанта. А выяснилось, что ни докладывать, ни консультировать нечего.

Но случилось удивительное – Гарри послушал выступления и предложения урезать PL/I ему понравилось. И он решил создать транслятор с этого подмножества для 8080, а потом и для 8086. Кстати, написаны они были на PL/M. Сейчас бы это подмножество назвали новым языком, но тогда так было не принято. Трансляторы PL/I-80 и PL/I-86 появились в 1982 и 1984 гг. Их и сейчас можно разыскать в сети. Позже подмножество было оформлено стандартом и ANSII и ISO.

Интересно, что в списке комитета по стандартизации X3J1 никакого Фрайбургхауса нет. Зато выражается благодарность Ершову, Корневой и Сахакяну за ценные замечания и предложения.

Итак, все было подготовлено к массовому использованию PL/I на ПК. Но IBM по не техническим причинам не стала снабжать свои IBM-PC трансляторами Килдэла и история ИТ пошла по-другому.

Поймите меня правильно, развитие средств программирование объективно требовалось бы все равно . И был бы в основе последующего развития языков на ПК PL/I или Си (как вышло в действительности), все равно были бы созданы языки типа Java или Rust или Питон. Только все бы это было быстрее и качественнее сделано без бесконечного изобретения заново. И не нужны были бы лямбды, поскольку была бы с самого начала блочная структура и вложенные процедуры))

Подмножество «G» PL/I получилось компактным и, на мой взгляд, более человечным, сохранив дух языка. Например, в языке если не указан куда идет ввод-вывод, он идет в стандартные файлы SYSIN и SYSPRINT. В реализации Килдэла по умолчанию SYSIN – клавиатура, SYSPRINT – экран. Ну, очевидно же и всем понятно. И без всяких заумных DD-операторов времен перфокарт.

В языке много всяких любопытных мелочей, вызванных проявившимися проблемами при практическом использовании. Например, есть ON-оператор обработки прерывания, REVERT-отмена обработчика (например при выходе из подпрограммы) и SIGNAL-имитация прерывания при отладке (и не только). Но потребовался еще оператор RESIGNAL. Он нужен вот для чего. Произошло прерывание и системная библиотека вызвала Ваш обработчик. Внутри обработчика анализируете данные и вдруг видите – мама дорогая! – это прерывание было не для Вас. В этом случае ставите в тексте обработчика RESIGNAL. Т.е. это не тот обработчик, ищи другой подходящий. Сейчас многие такие вещи само-собой разумеющиеся. Но полвека назад это было новаторство, которое потом и пришлось переизобретать.

Ну SYSIN и SYSPRINT и в обычной VM/CMS по умолчанию определялись на клаву и дисплей. Это было совершенно естественно.

Хорошо, что про британскую денежную систему напомнили! Читал об этом только применительно к самой ранней версии PL/I (в той самой старинной книге, в которой неизвестный доселе англоязычный термин "file" переводился русским(?) словом "тека"). Но возможно, что англичане этим пользовались и позже. В СССР не было на клавиатурах знака фунта, чтобы проверить.

RESIGNAL позже реализовала также IBM в PL/I Workstation и следующих компиляторах.

Трансляторы PL/I-80 и PL/I-86 появились в 1982 и 1984 гг. Их и сейчас можно разыскать в сети.

Я правильно понимаю, что это то, на основе чего потом был сделан упомянутый в статье  PL/1-КТ ?

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

свободно распространяемые компиляторы Iron Spring PL/I Питера Фласса для Linux (i386)

Я по сайту не понял, оно в исходниках распространяется (и если да, то какая лицензия) или только бинарно?

Компилятор Фласса распространяется в бинарниках, бесплатно для некоммерческого использования.

Фролов и Олюнин - это святое!

Вадим, спасибо тебе огромное (и, кстати, привет!).
Я учился программировать на PL/I (это было в маткружке и я был единственным учеником у двух преподавателей), моя первая программа была написана на PL/I (не работала, конечно, но сама возможность в шестом классе дойти до ЕС1060? и попробовать запустить простенькую программу казалось абсолютным чудом, первый Z80 я увидел года на три позже).

Sign up to leave a comment.

Articles