Необходимость разработки нового языка, а не копирование синтаксиса любого из существующих распространенных языков программирования обусловлено своеобразным исходным концептом, о котором мы сегодня расскажем. По нашему мнению, процесс создания и реализации концепта нового языка сам по себе является увлекательной и познавательной историей для всех, кто интересуется прагматикой языков.
Мы предполагаем, что для понимания изложенного материала читателю знакомы синтаксис и семантика нескольких распространенных языков программирования общего назначения.
Don't bite my finger, look where I am pointing
Warren S. McCulloch, 1960s
Исходная постановка задачи
Первоначально мы ставили задачу реализации технологии автономной генерации интерактивных задач для развития интеллекта человека, которая будет адаптивна к его успехам и неудачам. То есть, если человек успешно решает последовательность головоломок и результативен в прохождении игр, то эта технология перестраивает свои алгоритмы так, чтобы человек мог решать головоломки с новыми, более сложными условиями и непредсказуемыми вариантами игр, которые никогда в точности не повторяются. В случае неудач эта технология возвращает человека на уже знакомые позиции для перестройки программы своих способностей и готовности осуществить новый интеллектуальный подъем. По своей сути адаптивная программа здесь выполняет ту же работу, которую делает опытный персональный тренер в спорте.
В целом, при некоторых ограничениях, эта задача была решена еще в 2018 году приложением Helius' — full of life, которое можно найти в App Store.
В процессе реализации концепта Helius’ мы осознали, что для того, чтобы сделать следующий шаг и реализовать принципиально новый класс адаптивного приложения, которое было бы способно полностью трансформировать свою архитектуру, адаптируясь к способностям человека, нам необходим встроенный алгоритмический скриптовый язык, листинг которого может быть одинаково успешно сгенерирован в текстовой форме как самим приложением, так и человеком или может быть изменен человеком или другой программой на основе представленного кода в качестве исходного прототипа.
Мы сформировали главные требования и быстро сделали вывод, что концепт, ориентированный на выражение креативных потребностей разработчика, не очень удачно моделируется каким-либо из распространенных языков, потому что наша цель с некоторой долей шутки — программирование людей, а не компьютеров.
Так как хорошо спроектированной интеллектуальной системе одинаково «удобно» сгенерировать синтаксис любого непротиворечивого языка в любой форме, то для того, чтобы использовать язык коммуникаций между «искусственным» и «естественным» интеллектом логично использовать традиционный алгоритмический код, сделав его максимально удобным для паттернов мышления человека, знакомого с мейнстримовыми языками, предком которых был Algol. Это потомки по линии Pascal (Ada, Modula) и C (C++, Java, Swift). Впрочем, наш концепт построения абстракций и внимательное отношение к скобкам близки к духу Scheme (Lisp), а интеграция команд программного окружения в выразительные средства языка соответствует идеям скриптовых языков, винтажному BASIC для первых микрокомпьютеров и проекту Oberon – системы Никлауса Вирта.
Наша исходная цель также включает предоставление простого и удобного алгоритмического языка для быстрого программирования интеллектуальных игр и головоломок со временем обучения начинающего разработчика в течение одного — двух часов. При этом мы принципиально оставили в стороне визуальное конструирование объектов приложений в духе Scratch или макетирование игр в редакторах сценариев и визуальных объектов. На наш взгляд это уводит в сторону от творчества и выражения собственных идей, потому что креативные усилия скорее заменяются кропотливым изучением и преобразованием [графических] объектов. Предлагаем в будущем предоставить эту работу в качестве обучения алгоритмам для интеллектуальных агентов.
В итоге сформулируем назначение языка как обмен алгоритмами между искусственным и естественным интеллектом, совместное программирование задач (игр и головоломок) для себя и других, свободный обмен текстами программ.
Hi World
Перед тем, как мы обсудим основные тезисы для конструирования синтаксиса языка, представим три коротких фрагмента кода. Начнем с традиционной программы вывода приветственного сообщения:
PRINT “Hello world!”
Мы видим, что наш язык имеет скриптовый характер, предполагающий быструю интерпретацию; команда печати набрана в верхнем регистре и аргумент встроенной функции здесь не сопровождается скобками.
Представим два более интересных примера: нахождение для двух целых чисел наибольшего общего делителя в императивном стиле и с использованием рекурсии:
FUN gcd
INPUT a: INT
INPUT b: INT
WHILE a ~= b LOOP
IF a > b THEN a -= b ELSE b -= a ENDIF
REPEAT
PRINT “gcd = “, a
RETURN
FUN gcd _ a: INT, _ b: INT -> INT
IF b == 0 THEN RETURN a ENDIF
RETURN gcd b, (a % b)
PRINT gcd 6, 9
# печатает 3
Требования для языка Hi
Каждый удачный язык проектируется с конкретным назначением, которое определяет его синтаксические особенности и семантику. Например, ASSEMBLER был предназначен для прямого кодирования команд процессора в мнемонической форме, удобной человеку; BASIC (тот который с номерами строк и оператором GOTO) — удачно продолжил идею прямой трансляции команд высокоуровневому интерпретатору. Hi programming language предназначен стать языком команд и алгоритмов для коммуникаций между Human и абстрактным интеллектом некоторой спроектированной системы.
Как логичное следствие, программным кодом считается не скомпилированный машинный код и не байт-код виртуальной машины, а исходный текст (листинг), включая комментарии. Соответственно, внутренняя реализация кода никоим образом не стандартизируется и оставляется на усмотрение реализации интерпретаторов или компиляторов.
Итак, наша главная цель – обмен мыслями при помощи формального языка. Введем три основных требования:
- Язык должен быть легким в освоении человеком
- Язык должен быть надежным в использовании
- Язык должен быть способен к организации очень сложных программных систем.
Давайте подробнее исследуем эти базовые требования.
Язык должен быть легким в освоении
1) Мы будем использовать написание синтаксических конструкций с возможностью легкого прочтения, хорошо знакомое любому современному образованному человеку. Мы будем избегать экспериментов с краткостью в ущерб прочтению, как это сделано, например, в языке APL.
Следовательно, мы используем конструкции вида
LOOP…REPEAT
, а не {…}
. Фигурные скобки в качестве приятного бонуса будем использовать вот так:s = {1, 2, 3}
у нас будет обозначать присвоение переменной s множества из трех целых чисел.a = [1, 2, 3]
у нас будет обозначать присвоение переменной a массива из трех целых чисел.Использование продуманных кратких языковых конструкций также позволяет сделать более логичную связь семантики и синтаксиса языка без использования контекста окружения. Подробнее обсудим этот тезис позднее при рассмотрении конкретных языковых конструкций.
2) Для арифметических выражений используем запись вида:
a + b + c
, а не (+ a b c)
, как в семействе LISP. 3) Природа нашего скриптового языка требует встроенной библиотеки всех необходимых функций в окружение языка без необходимости подключения внешних фреймворков.
4) Используем нативную алгоритмизацию паттернов мышления: императивный язык с возможностью рекурсивных функций и элементами функциональных вычислений. Как мы в дальнейшем рассмотрим на одном из примеров, императивный стиль вполне удобен и для программирования приложений, основанных на архитектуре обработки событий в декларативном ключе, подобных SwiftUI. Иначе говоря, термины «императивный» и «декларативный» скорее отображают позицию наблюдателя, чем реальность, данную нам в ощущение.
5) Следим за отсутствием избыточности синтаксических конструкций и эргономичностью ввода символов кода. Мы используем новую строку в качестве разделителя. Впрочем, как и в языке Swift, можно использовать «;» в одной строке в качестве разделителя для нескольких выражений. При этом наше инженерное образование категорически протестует против придания синтаксической значимости отступам в тексте программы, как это ранее использовалось в Fortran, а в настоящее время в Python.
Надежный язык
Необходимым условием существования сложных систем во времени является требование полной семантической однозначности и исполнимости каждой корректно написанной программы без исправлений при неизбежной эволюции и усложнении языка в будущем. Как решить эту проблему надежности работы программ при его неизбежном развитии и расширении? Для того, чтобы избежать конфликта совпадения идентификаторов и зарезервированных слов мы используем простой и эффективный способ, как сделано, например, в Oberon. Язык HI резервирует все идентификаторы с буквами в верхнем регистре без цифр с количеством символов более одного как служебные. Таким образом допустимыми идентификаторами, написанными вручную программистом или сгенерированными интеллектуальной системой, являются следующие примеры:
foo, Foo, f_001, F1, F, for
Примеры идентификаторов, которые зарезервированы языком:
FOO, FOR, HI, YES, EVERYRESTRICTIONMATTER
В целях надежности кода мы используем статическую типизацию с возможностью автоматического вывода типов из деклараций вида:
LET x = 6 # константа x имеет тип INT
VAR boolean = TRUE # объявление переменной boolean типа BOOL
Мы различаем константы и переменные не для оптимизации времени исполнения кода, а для того, чтобы разработчик хорошо понимал предназначение тех объектов, которыми он управляет.
Язык для построения очень сложных программных систем
Архитектура любого универсального языка должна предусматривать продуманную возможность построения сложных программ из небольших автономных кусочков, допустим до 250 LOC в одном источнике, ибо, да простит нас уважаемый Читатель, только сканеру и парсеру легко работать с исходными текстами любой сложности, а человеку даже перемножить пару трехзначных целых чисел тяжело без калькулятора.
Мы пока оставим в стороне реализацию архитектуры сложных систем на языке Hi и детально разберем эти вопросы в последующем при изложении концепции организации классов – протоколов, коммуникации между ними и способам построения их иерархии. Заметим только, что архитектура сложных приложений будет строиться из небольших автономных, легко читаемых и модифицируемых фрагментов и вдохновение здесь мы черпали не в изучении computer science, а в архитектуре взаимодействия клеток и органов живых организмов. Тело обыкновенного человека составляет порядка 50 триллионов жизнеспособных клеток, успешно функционирующих несмотря на сложные окружающие условия, наличие множества паразитов и постоянные повреждения миллионов микрокомпонентов. Создателю компьютерной системы, которая полностью прекращает свое функционирование вследствие единственного обращения к несуществующему индексу массива здесь есть чему поучиться.
Хотя мы начинаем с изложения скриптового языка, удобного для быстрого и приятного программирования небольших головоломок, мы должны дать полную уверенность разработчикам, что на платформе однажды разработанных простых программных компонентов без использования сторонних решений в будущем они смогут строить жизнеспособные системы неограниченной сложности.
Ограничения
На практике всегда, когда мы применяем какие – либо требования для конструирования систем, то чем-то приходится или жертвовать или на что-то не обращать внимания. Для нас в конструкции языка Hi не имеет принципиального значения:
- Точная совместимость синтаксиса с другими языками программирования
- Возможность использования существующих внешних библиотек кода или интеграция с другими программными системами
- Оптимизация скорости исполнения программ c ориентацией на аппаратные возможности и n-битную архитектуру компьютеров
- Сложность реализации интерпретаторов / компиляторов для полной версии языка
- Так как исполняемый интерпретатором программный код — это исходный текст, то не предусмотрено встроенной защиты от копирования и легкой модификации в случае наличия доступа к источнику
В завершение скажем о происхождения приветливого наименования языка программирования HI, Hi или hi. Допустим это будет Helius’ interactive Programming Language или Human Intelligence Programming Language. В отличие от всех конструкций нашего языка это единственный мета — идентификатор, который не имеет однозначной семантики.
В следующей статье представим описание Hi Basic Programming Language «на одной» странице и затем разберем логику конструирования синтаксиса, следуя требованиям тезисов, представленных выше.
NightShad0w
А поясните задачу подробнее, сжатое абстрактное описание не совсем объясняет зачем понадобился еще один новый узкоспецифичный язык, дублирующий уже имеющиеся.
Хотелось бы увидеть модель взаимодействия между человеком и программой, и объяснение, какие инструменты необходимы, но не доступны в языках общего назначения.
SergeKotov Автор
Да, конечно, современные языки c IDE вполне комфортны для профессиональной работы. Но мы решили не использовать один из наиболее симпатичных. Довольно много причин, поясню может быть самые существенные основания конструирования отдельной грамматики:
1) Во-первых, совершенно неправильно реализовать отдельный интерпретатор для узкого подмножества или модифицированного множества грамматики существующего языка и назвать его тем же именем. Нам не нужна полная мощь вариативность конструкций современного языка, особенно «мульти – парадигменного» со всей трудоемкостью поддержки стандарта в некоторой версии.
2) При эволюции существующих языков совместимость с прошлыми версиями может быть нарушена. Наше базовое требование – полная работоспособность исходного текста (не байт-кода) при неизбежном расширении языка. По этой причине мы зарезервировали под верхний регистр все служебные идентификаторы, чтобы исключить риски попадания пользовательских идентификаторов в будущие зарезервированные слова.
3) У нас своеобразная концепция связывания компонентов в крупные системы, которая пока прямо не воспроизводится в известных нам языках. Частично это можно решить вариантом с условной компиляцией и директивами (pragma) интепретатору, но в итоге получится не очень хорошо.
Чтобы сделать низким порог освоения мы используем хорошо знакомые всем синтаксические элементы, в нескольких следующих статьях расскажу о логике отбора. Здесь я вдохновлен циклом Robert Nystrom по Lox Language craftinginterpreters.com/the-lox-language.html Даже если вы не совсем согласны со всеми тезисами автора по его учебному языку Lox, но сама работа просто замечательная.
SergeKotov Автор
Очень хороший вопрос про модель взаимодействия между «человеком» и «программой», собственно с этого всё началось. Мы еще не завершили эту работу даже в концепте, чтобы представить готовый вариант, нам как раз нужен формальный язык для интерпретаций. Если совсем упрощенно, то программы, написанные человеком анализируются и на их основе формируются другие, которые, в свою очередь могут быть модифицированы человеком или другим алгоритмом. Технология не связана с «машинным обучением» в том виде, как его сейчас понимают, но тоже использует семплы в данном случае исходного кода для задач обучения.
NightShad0w
Как я понимаю, ваша цель — создать систему, которая может корректировать сама себя, при этом начальное зерно задается программистом. Т.е. утрированно, вы создаете высокоуровневый аналог примитивной машины Тьюринга, которая идя по ленте, технически может перезаписывать части этой ленты для адаптации программы на лету.
Но мне все равно не совсем ясна задача, которая будет решаться такой системой. В статье как пример используется gcd. Мы хотим от системы, которой на вход подана gcd реализация, некоторой трансформации этой программы. Как будет проведена трансформация — на уровне исходного кода, байт кода или ассемблерных инструкций нам не принципиально. Но почему мы хотим трансформацию, почему конкретную трансформацию, и что мы получим как результат трансформации — вот на эти вопросы хотелось бы получить ответы.
SergeKotov Автор
Так как мы по определению обладаем интерпретатором, то для случая таких функций как gcd с детерминированным поведением вполне можно умозрительно продумать цель трансформации как получение результата функции с меньшими ресурсами и за меньшее время путем естественного отбора из многих доступных функций с изоморфным поведением. Получив лучшего кандидата, мы можем просто заменить или хотя бы предложить это сделать для менее удачных функций с аналогичным поведением. Уже для этого простого случая мы упираемся в главную проблему. Для выполнения любой адаптации, начиная с распознавания изоморфного поведения мы должны обладать более сложно организованной системой и нет никакой возможности здесь «самому переписать себя». Цели для любой адаптивной системы задаются внешним окружением по отношению к самой системе.
Есть гипотезы, как частным образом решить эту главную проблему для программ, близких к игровым. Здесь очень нужен язык, удобный для экспериментов. Именно поэтому важны те тезисы, которые описаны в статье, в том числе по абсолютной исполнимости однажды написанных программных компонентов при расширении языка.