Универсальный язык программирования. Ноу-хау

Эксперты нам разумеется объяснят, что универсального языка быть не может, ибо одним надо одно, другим другое… Но давайте посмотрим, как всё это разнообразие будет реализовываться в компьютере. А там должно быть указано действие, которое надо выполнить с некоторыми данными. Т.е. операция и операнды. Но именно так и устроены языки программирования, называемые ассемблер. Этот перечень машинных инструкций и языком-то не назовёшь. Но именно такой "язык" и мог бы стать универсальным.

Ввиду практической необходимости, первым языком, который научили понимать ЭВМ, был язык математических формул и назывался он FORTRAN (FORmula TRANslator). Под предлогом различного прикладного применения ЭВМ стали придумывать самые разнообразные языки. Хотя конечным "объектом их приложения" конечно был всё тот же ассемблер.

И все эти, похожие на человеческую речь, фразы конечно же описывали всегда две части инструкции: 1-требуемое действие и 2-перечень аргументов с коими его следует выполнить – так же, как и в ассемблерах: <Что делать>(<перечень над чем>) – но красиво. Как бы облегчая труд программирования, изобрели множество языков, которые мало соответствовали машинным технологиям, но были отчасти близки к разговорным человеческим.

Придумали даже бухгалтерский COBOL, как будто бухгалтеры займутся программированием. В нём логические и арифметические операции записываются человеческими словами типа "plus" или "multiply" вместо "+" и "·". А сейчас ищут кого-нибудь, кто его пока помнит, потому что какие-то банкоматы запрограммированы на нём.

Современные изобретатели языков всё более отдаляют в область абстракций описание алгоритма от способа его машинной реализации. Цитата:
«Лямбда-исчисление, разработанное А. Чёрчем для преодоления некоторых проблем в математической логике, фактически служит теоретическим базисом функционального программирования».

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

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

Но понятно, что подоплёка всех подобных тенденций состоит в возможности занять новые ниши ИТ в которых можно стать первыми, и получить возможность сделать массу публикаций, защитить учёные степени и организовать новую школу и направления. Других "объективных" причин размножения языков программирования нет. Все проблемы ИТ вполне можно решить в рамках единого языка. И даже вроде пытались сделать это на основе языка ADA, но всего лишь добавили разнообразия.

А вот в СССР, наоборот, логичное стремление к унификации и объединению усилий в разработках ЭВМ и ПО, ради чего отечественные ИТ в 70-е годы переориентировались на копирование американской IBM System/360, привело практически к уничтожению собственных разработок, как программных средств, так затем и микросхем, т.е. была уничтожена ВСЯ отечественная сфера ИТ. Ну разве только военные что-то там замутили с "Эльбрус".

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

Но поскольку задачи внедрения унифицированной базы данных (УБД) и децентрализованной параллельной обработки данных по-прежнему актуальны, следует подумать и об адекватном языке программирования. Естественно, в нём не должно быть популистских конструкций похожих на разговорные фразы.

Вообще-то, его и придумывать не надо. Он есть изначально, а проблема-то как раз в том, чтобы придуманного было как можно меньше, ибо каждая лишняя "придумка" сужает спектр возможностей. Итак, основными понятиями языка являются оператор, операнд, скобки, разделители и метки. Всё! Скобки отделяют оператор от операндов, разделители разделяют операнды, а посредством меток передаётся управление.

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

И конечно он должен быть применим для программирования потоковой параллельной обработки данных. Главное, чтобы все подпрограммы различного прикладного применения этого языка были хорошо документированы и доступны благодаря УБД и метаданным.

Резонно спросить: что же в этом "языке" нового, чего нет хотя бы в ассемблере? А Ноу-хау в том, что в этом языке нет описаний. Он состоит только из операторов, исполняемых при запуске программы после трансляции и загрузки, после чего программа готова принимать и обрабатывать данные. Напомню, что управление осуществляется по данным. Поэтому наличия каких-либо операционных систем не требуется.

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

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

В скобках <…> приводится описание или обозначение того, что в реальном тексте программы должно бы быть на их месте. Пусть ряд любых символов заключённый между апострофами означает комментарий. Равноценными разделителями данных, операторов и конструкций языка пусть будут символы в кавычках: «,» «;» «.» «пробел» «комментарий» – в любом количестве и сочетании подряд. Но применение различных их видов позволит акцентировать структуру программы и вид данных. Пусть далее <п> означает «пробел», а <р> – любой разделитель.

Почти все действия с переменными и вообще данными должны выполняться операторами (процедурами) вида <оператор><лев_скобка><перечень операндов><прав_скобка>, где <операнд> это данное или оператор, а скобки это «( [ {» и «} ] )». Операнды в перечне отделяются друг от друга разделителями.
Для некоторых операторов указывать скобки не нужно.
Это смена знака «–<данное или оператор>», метка «:<имя метки>», переход на метку «go<п><имя метки>», получение адреса «@<данное или оператор>» и получение данного посредством косвенной адресации «~<pointer>», где <pointer> это данное содержащее адрес нужного данного.

Оператор присваивания данного или их массива мог бы выглядеть так:
«=(<данное получатель>'='<перечень операндов>)».
А операторы сложения, умножения и деления так: «+(<перечень операндов (слагаемых)>)», «*(<перечень  операндов (сомножителей)>)»
и «/(<делимое>'/'<делитель>)».
Например, выражение "a+b*c/d-e" может быть записано как:
"+(a /(*(b c)d)-e)" или "+(a'+'/(*(b'*'c)'/'d)-e)". Если операции выполняются с целыми числами, то скобки должны быть квадратными «[...]».
Ну и т.д.

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

Достаточно ограничиться минимальным набором типов переменных: byte, binary, pointer, int, real, datetime, boolean, text.
Пусть binary, это 8-ми байтовое двоичное число без знака, причём в контроллерах можно использовать для binary четыре или два байта.
Тип pointer введён, чтобы предотвратить доступ к ячейкам памяти вне компетенции исполняемой программы.
Строки и константы различаются формой представления и размещаются в памяти по мере трансляции текста и загрузки.

Логично определить несколько исходных видов данных, начиная с простых переменных указанных выше типов, описываемых в программе как var(<тип>(<перечень>) ... , <тип>(<перечень>)).
Добавим к ним определения массивов arr([<количество элементов>], <тип>(<перечень>) ... , <тип>(<перечень>))
и матриц mat([<столбцов><п><строк>], <тип>(<перечень>) ... , <тип>(<перечень>)).
Здесь и далее в квадратных скобках указываются данные целого типа, а перечень содержит имена объектов описываемого вида и одного типа разделяемые, например, пробелами. В общих скобках описываются объекты такого же вида и размерности, но различных типов.

Чтобы данные описанных видов появились в программе и с ними могли выполняться действия, надо их реально создать, т.е. выделить для них память, что выполняется оператором dec(<вид данных> ... , <вид данных>).
Иногда нужно описать данные, но не выделять им память. Пусть тогда будем применять оператор создания виртуальных данных vdec(<вид данных> ... , <вид данных>).

Теперь опишем вид данных структура: str( dec(<вид данных> ...) <перечень структур>). Каждое наименование данного, указанного в перечне видов данных оператора dec, здесь является полем структуры.
Разные структуры могут иметь совпадающие составы полей одинаковых наименований. Структуры иногда не нужно создавать, а требуется назначать (накладывать) на область неких данных используя pointer:
=(<имя структуры>'='<pointer>). Для них следует применять оператор для виртуальных данных vdec(…).
Структуры со всеми полями могут быть уничтожены оператором dels(<перечень имён структур>).

В свою очередь, получение значения данного каждого вида, кроме var, должно тоже выполняться посредством операторов. Элемент массива выдаётся оператором
a(<имя массива>;<номер элемента>), где <номер элемента> от 0 до N-1, и N=количество элементов в массиве.
Аналогично, элемент матрицы m(<имя матрицы><п><столбец>,<строка>).
Элемент структуры получим как s(<имя структуры >.<имя поля>). Здесь «.» всего лишь просто разделитель.
Если поле массив или матрица, то применим к нему операторы описанные выше.

Операции, выполняемые с данными, игнорируют их тип и всегда выполняются с полным словом binary. Тип результата зависит только от применённого к ним оператора.

Мне кажется целесообразным так определить операторы (функции):
Функции преобразования типа: i(<операнд или оператор>) – в целый тип без округления (кроме, если дробь двоичная "1" в периоде)
и r[<операнд или оператор>] – в real с пл.точкой.
Операторы чисел с плавающей точкой: +, *, /, ^ (последнее – возведение в степень).
Операторы с целыми числами: ++, **, // (последнее – деление нацело).
Двоичные операторы: OR, AND, XOR, NOT, >>, <<, <+, >+ (последние – сдвиги и циклические сдвиги на указанное число разрядов).
И ещё должна быть функция Err(k), которая возвращает значение ошибки типа "деление на ноль" или "переполнение", либо несоответствие типов, несуществующий оператор или операнд и пр. Параметр "k" определяет код или класс контролируемых ошибок.

Кроме того, в языке используются метки, изображаемые набором символов и чисел после двоеточия.
Соответственно, должны быть операторы перехода, например, «go <имя метки>», или условного перехода «==(<операнд> <имя метки>)», который, в случае если переменная или выражение имеют результатом нулевые значения во всех разрядах, выполняет переход по адресу метки.
Может быть и условный оператор «><(<операнд> <имя метки>)» – если хоть один разряд не 0, то …
Специальные операторы цикла, как видим, не понадобятся.

И, наконец, описания новых процедур или функций:
Code{(<имя процедуры|функции>;<спецификация>(<перечень формальных параметров>), …) "<перечень операторов>"}. Функция от процедуры отличается только занесением или нет результата. Спецификация определяет способ вызова параметров: "value" – по значению, "name" – по наименованию, "proc" – процедура, "func" – функция.

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

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

Близкое соответствие формата операторов языка типовым машинным командам позволило бы легко его адаптировать к ЭВМ с любой архитектурой и даже без использования громоздких ассемблеров. Простота синтаксиса языка позволяет транслировать его в машинный код в процессе загрузки, что, при стандартизации состава и форматов системных параметров, обеспечивает 100% совместимость.

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

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

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

В последние десятилетия практически безвариантно принята концепция структурного программирования, согласно которой запрещено применять команды перехода (goto) на метки (label) программы. "Это де облегчает понимание и отладку программ".
Однако, сколь бы ни была программа "структурирована", компилятор всё равно вставит команды перехода – и условные, и безусловные. Полагаю, что и программисты смогли бы сделать это не хуже.

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

И, разумеется, найти метку в программе в сто раз легче, чем отследить, несмотря на отступы, начала и концы вложенных выражений if then else.

Для примера покажу, см. рисунок, как с метками выглядит программа для блок-схемы приёма данных (см. http://proza.ru/2024/10/11/770). Позиции меток там показаны выносками с цифрами и двоеточием в красных рамках.

Как видим, всё приветствуемое "на ура" развитие языков программирования имеет чисто академический интерес и, существенно усложняя их практическое использование, объективно тормозит прогресс. Увы ...


Рецензии