Комментарии 59
Вроде как он -- первая попытка создать универсальный язык программирования на все случаи жизни, до этого были лишь более-менее специализированные -- скажем, Фортран и Алгол для научных задач и Кобол -- для экономических.
Надо отсканировать как-нибудь свои запасы бумажных документов... В частности, есть все тома по ПЛ/1 для ОС ЕС. Практической пользы 0, но исторический интерес представляют.
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)
следует пояснить, что в данном случае означает "нестрогая типизация".
Указатель в числовую переменную, например, в языке присваивать нельзя.
А выражение '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, если писать его на современный манер, был бы просто огромным :)
Для меня 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 и следующих компиляторах.
Какой кошмар...
У меня в студенчестве была книжка по сабжу Фролова и Олюнина, зачитанная до отрыва корешка. Интересовался, планировал на нём писать, но поскольку до рабочего компилятора так и не добрался, в итоге оседлал сначала объектный Паскаль, а потом плюсы. Всегда удивлялся, почему в современных языках не все очевидные и удобные вещи оттуда реализовали. Кажется, только в Go и последних стандартах плюсов лёд тронулся.
свободно распространяемые компиляторы Iron Spring PL/I Питера Фласса для Linux (i386)
Я по сайту не понял, оно в исходниках распространяется (и если да, то какая лицензия) или только бинарно?
Вадим, спасибо тебе огромное (и, кстати, привет!).
Я учился программировать на PL/I (это было в маткружке и я был единственным учеником у двух преподавателей), моя первая программа была написана на PL/I (не работала, конечно, но сама возможность в шестом классе дойти до ЕС1060? и попробовать запустить простенькую программу казалось абсолютным чудом, первый Z80 я увидел года на три позже).
Язык PL/I: десять тонн синтаксического сахара