Как стать автором
Обновить

Комментарии 59

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

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

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

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

В очередной раз сошлюсь на мой любимый сайт со сканами старых документов, вот небольшая подборка:

Спасибо, ценнейший ресурс!

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

Польза от того, что там какая-то библиотека какой-то версии всё равно подключится нивелируется тем, что оно сломается не в 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)

↑ PL1LFCG, конечно.

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

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

А выражение '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 я увидел года на три позже).

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории