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

Моя первая программа Флаббергаста была не очень хороша. К сожалению, это также компилятор Flabbergast. Упс. Мне стало лучше, точно так же, как когда я использовал языки программирования, которые не изобретал.

История

Когда я работал в Google, мне приходилось использовать проприетарный язык под названием GCL. По причинам NDA я только повторю то, что уже является общедоступным в частично отредактированной статье GCL Viewer.

GCL активно использовался в моей роли, и почти все его ненавидели. GCL был настоящим монстром Франкенштейна. В нем были некоторые части, которые смутно напоминали JavaScript, некоторые очень функциональные элементы LISP, поведение объектов в стиле Java, специфический синтаксис, похожий на C, и некоторые идеи, которые действительно пришли ни с того ни с сего. В частности, «контекстный поиск» во Flabbergast исходил от GCL, который якобы был создан людьми, обрабатывающими естественный язык. Проблема, знакомая любому программисту на C++, заключается в том, что когда разные парадигмы объединяются, на границах возникают запутанные вещи. Например, GCL был своего рода декларативной функциональной утопией без побочных эффектов, которая выводила из себя программистов на Haskell, но в нем была обработка ошибок, которая могла вызвать недетерминированное поведение.

Программисты на JavaScript и C++ довольно хорошо адаптировались к своим недостаткам: у них есть шаблоны, соглашения и эмпирические правила, чтобы избежать и устранить определенные категории ошибок и непреднамеренных эффектов. GCL не хватало этого по социальным причинам. Документация GCL была не очень полезной, и был написан плохой код. Когда начинающим программистам что-то нужно было, их лучшим ресурсом был корпус плохого кода, и каждый старался писать как можно меньше GCL, поэтому наполовину приличный GCL составлял меньшую часть кода. Это увековечило цикл, когда большинство примеров GCL были плохими и трудными для понимания.

Несколько человек, в том числе и я, придерживались мнения Дугласа Крокфорда, автора книги JavaScript: The Good Parts: да, GCL в целом был огнем покрышек, но в нем были похоронены хорошие идеи. . То, что делало GCL хорошим, было действительно странным. Это были особенности, которых я не нашел ни в одном другом языке. Я работал над созданием руководства по шаблонам проектирования для GCL и других учебных материалов. Было очень приятно, когда многие ненавистники GCL начали видеть ценность GCL после того, как они приняли странное. Я начал смотреть GCL с некоторой жалостью.

Это не аргумент в пользу того, что GCL хорош. Это объективно плохо… просто не безнадежно плохо. То, что делало его мощным, выразительным и полезным, казалось странным — действительно странным. Авторов GCL легко простить, поскольку они не могли предвидеть результаты своих решений.

Например, контекстный поиск казался частью хорошего, но это форма динамического обзора. Возьмите учебник по языкам программирования, и в нем может быть небольшой анекдот о том, как динамическая область видимости была одной из тех смехотворно плохих идей, которые были мертвы с 80-х годов и существуют только в EmacsLisp и R по историческим причинам. Каким-то образом контекстный поиск действительно работал в GCL. Это не было бременем или ошибкой. Это была полезная функция, несмотря на общепринятое мнение.

С Flabbergast я собирался принять странное. Я хотел знать, было ли мое разделение на хорошее и плохое «правильным» (то есть полезным). Идея заключалась в том, чтобы избавиться от всех плохих функций GCL и начать только с хороших. Оттуда двигайтесь наружу, пока язык не сцепится. Я был полностью согласен брать другие идеи, которые мне нравятся, из мест, которые люди ненавидят. JavaScript, VisualBasic, XQuery, SQL и Haskell послужили источником вдохновения, и по крайней мере один из них вызовет у программиста тошноту.

Я не был уверен, что хочу сделать. Часть меня хотела сделать жизнеспособный язык. Это тоже своего рода арт-проект — статуя, сделанная из колпаков и хлама с обочины дороги.

Я начал проектировать. Одной из первых вещей, которые я убил, были все концепции функционального программирования, несмотря на то, как сильно я люблю функциональное программирование… полностью удалив функции и лямбда-выражения. Люди часто используют функциональное и декларативное взаимозаменяемо. Flabbergast является декларативным, но определенно не функциональным. Уничтожение функций потребовало удаления из языка LISP-подобных операций map/filter/reduce. Большая часть кода GCL сильно зависела от них, но они требуют функций. Мне нужна была альтернатива.

Решением стало смешение XQuery и SQL. Эти языки преобразуют XML-документы или таблицы соответственно. Синтаксис SQL — неуклюжий продукт 70-х годов, поэтому большинство людей его ненавидят. Концептуально SQL действительно удивителен: опишите данные, которые вам нужны, из имеющихся у вас данных, и он поймет, как это сделать. C# имеет встроенную систему запросов под названием LINQ, которая определенно является детищем того, кто любит SQL, и большинство программистов на C# восхваляют LINQ и демонизируют SQL в одном и том же предложении. XQuery имеет приятный синтаксис FLOWR, почти наверняка являющийся источником синтаксиса LINQ. Выражение Flabbergast fricassé — это, по сути, FLOWR XQuery с некоторыми другими особенностями.

Книга обугленный дракон

За долгие выходные я написал оригинальный интерпретатор Флаббергаста на Вале. Это было медленно, глючно и ужасно, но достаточно для экспериментов. Я начал играть. Многое из первоначального синтаксиса, который я разработал, было уродливым, и я начал его пересматривать. Когда я создавал язык, я скептически относился к общепринятым чертам других языков. Примите странное.

После нескольких тестов я решил, что пора написать новый компилятор. Интерпретатор на основе Vala работал очень медленно (частично из-за того, как Vala управляет памятью, а частично из-за моего дизайна). Цель состояла в том, чтобы ориентироваться на JVM и CLR. Эти виртуальные машины могли бы сделать больше для оптимизации, чем я один.

Обычно уважающий себя компилятор пишется на языке, который он компилирует (например, компилятор C написан на C). Это казалось правильным местом, чтобы пойти. Она также обещала стать самой большой программой, написанной на GCL или Flabbergast. Самым сложным вопросом было, как написать компилятор на таком странном языке. У него не было средств манипулирования строками для парсинга чего-либо.

Затем началось странное. Флаббергаст в основном хорош в рендеринге вещей. Не рендеринг в графическом стиле, а составление различных битов информации для создания некоторой расширенной формы. Воспринимайте шаблоны HTML, XSLT, генерацию байт-кода, генерацию схемы или расширение макросов как «рендеринг» исходного файла для вывода. GCL в основном использовался для того, чтобы взять сложную логическую конфигурацию и свести ее к буферам протокола (эквивалент Google JSON). «Компилятор» Flabbergast состоит из двух частей: объяснение того, как работает язык, построенный из небольших операций, и то, как вы могли бы реализовать эти небольшие операции на Java или C#. Когда вы создаете компилятор, он преобразует объяснение языка в большую строку: программа на Java или C#, которая знает, как компилировать Flabbergast, построенная путем составления всех этих небольших операций. Это не обычный способ создания компилятора. Однако это работает. На самом деле, я добавил новый синтаксис во Flabbergast без написания C# или Java.

Функции: Лучшая битва, чтобы проиграть

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

В GCL были лямбда-выражения и шаблоны. Почти все, что может сделать лямбда, может сделать шаблон… и шаблоны могут еще больше.

Шаблон — это своего рода прото-объект, где объект во Флаббергасте называется frame и является неизменяемым словарем. Я выбрал имя frame, потому что object имеет слишком много ментального багажа из объектно-ориентированных языков. В большинстве ООП-языков объекты служат для инкапсуляции данных и смешивания кода (методов) и данных (полей). Во Флаббергасте это не так. Флаббергаст не делает этого различия: все атрибуты в фрейме являются значениями, но вычисляются из выражения. Для объектно-ориентированного программиста это означает, что атрибуты фрейма могут быть прочитаны только так, как если бы они были полями, но каждый из них записывается так, как будто это метод без параметров.

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

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

Я понял, что это очень похоже на дебаты между анонимными внутренними классами Java и делегатами C#. Изначально в Java не было ничего похожего на первоклассную функцию. Когда Microsoft начала расширять Java до того, что впоследствии станет C#, они добавили делегаты, которые представляли собой замыкания, которые можно было вызывать как функции. Разработчики Java не одобряют. В Java была функция, называемая анонимными внутренними классами (AIC), у которых были некоторые свойства делегатов (они были замыканиями), но они также были обычными объектами, а не новой категорией вещей. AIC действительно может делать все то же, что и делегат, и даже больше. Большая проблема с AIC в Java заключалась в том, что синтаксис для их использования был очень подробным. Аргумент шаблон против лямбды почти такой же: шаблоны могут делать все то же, что и лямбды, только с более неудобным синтаксисом. Итак, с соответствующим синтаксическим сахаром мы можем создавать лямбда-выражения из шаблонов и получать простоту как в дизайне, так и в использовании. Java дождалась появления Java 8, чтобы добавить этот сахар. Я сдался гораздо быстрее.

С синтаксисом вызова функции, привитым поверх шаблонов, обработка возвращаемого значения была простой: шаблон, подобный функции, должен был иметь атрибут value, содержащий возвращаемое значение. Однако шаблоны имеют только именованные аргументы (переопределения на языке Флаббергаста). Большинство языков имеют позиционные аргументы при вызове функции, так как же позиционные аргументы будут преобразованы в именованные переопределения?

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

В других языках есть вариативные функции: функция, которая может принимать произвольное количество аргументов, и к ним можно обращаться как к списку. Опять же, Флаббергаст использует странное для элегантного решения: безымянные аргументы обрабатываются как вариативные и помещаются в список с именем args. То есть позиционные аргументы всегда вариативны. Любым другим аргументам должны быть даны имена во Flabbergast, а именованные аргументы могут появляться в любом порядке. Запоминание правильного порядка аргументов — это просто то, чего никогда не нужно делать программистам Flabbergast. Это странно, но в некотором роде приятно и, конечно, менее неловко, чем ситуация в Python или R, где именованные и позиционные аргументы могут смешиваться, что приводит к запутанным вопросам, например: можно ли пропустить позиционный аргумент, если я назову его позже? Во Флаббергасте эти два вида аргументов не пересекаются.

У него была одна неотложная проблема: функции с переменным числом аргументов не могли вызывать друг друга. В Java и C# переменные аргументы передаются в виде массива. Это означает, что есть два способа вызвать функцию с переменным числом аргументов: с набором элементов, которые автоматически упаковываются в массив, или с одним массивом. Если ваша функция принимает массив массивов, все может запутаться, но система статических типов может разобраться в таких ситуациях. В Python система динамических типов не может помочь, поэтому Python обходит это, используя специальный синтаксис для передачи списка в качестве вариативных аргументов. PERL делает зеркало с помощью специального синтаксиса для передачи списка в качестве непеременного аргумента, поскольку PERL.

Flabbergast на самом деле имеет встроенное решение: просто передавайте аргументы с переменным числом аргументов как именованный аргумент с именем args, так как это действительно то, что мы делаем с аргументами с переменным числом аргументов в любом случае. Единственное дополнительное правило, которое необходимо компилятору, состоит в том, что одновременное использование вариативных аргументов и именованного аргумента args является незаконным.

Именно тогда я понял, что изучаю этот язык. Может быть, обнаружить его? Я как бы перестал его изобретать. У него были особенности, и я обнаруживал их и исправлял ошибки в своем компиляторе.

Проблема уже решена

Еще одна вещь, с которой я боролся, — это возможность объединять шаблоны. GCL позволяет «добавить» два шаблона, объединяющих все атрибуты. Это привело к всевозможным странным проблемам: она изменила семантику вычислений, часто непонятным образом, не могла разумно обрабатывать алмазное наследование и не могла рекурсивно объединять вложенные структуры. Тем не менее объединение шаблонов было настолько обычным явлением в GCL, что я не представлял, как мне избежать этого.

Потом я понял, что Флаббергаст уже поддержал это, и мне не нужно было ничего делать. Это действительно было похоже на открытие.

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

В итоге я назвал такие шаблоны -ifiers. Если у вас есть шаблон, который возводит значения в квадрат, а затем вы хотите их суммировать, вы можете запустить его через «сумматор», чтобы получить шаблон суммы квадратов. На самом деле в стандартной библиотеке шаблон sum является шаблоном identity, запускаемым через файл summifier. Если вы хотите составить сумму кумулятивных произведений квадратов, все части находятся в стандартной библиотеке.

Существует целый построитель SQL-запросов, который представляет собой просто -ifier, который либо изменяет шаблон, способный выполнять поиск в базе данных, либо шаблон для автономной генерации запросов.

Награды за ревность

Особенностью, с которой я долгое время боролся, была индикация типа. Во Flabbergast всегда были простые проверки типов, такие как x Is Int, но не было операций, подобных typeof в JavaScript, type в Python, ref в PERL, .class в Ruby, .getClass() в Java или .GetType() в C#. Я хотел такую ​​вещь, но я хотел, чтобы это было хорошо.

Мне очень не нравилось решение JavaScript и PERL возвращать строку. В Python, Ruby, C# и Java есть операции, возвращающие тип типа. Это имеет смысл в языках, где можно было бы определять новые типы, но это было невозможно во Flabbergast, поэтому добавление типа типа было бы не намного полезнее, чем возврат строки. По сути, вы ничего не можете сделать с типом шрифта во Flabbergast, кроме как сравнить их.

У компилятора была внутренняя операция «отправки». Он будет принимать значение любого типа, а затем запускать другой код для каждого возможного типа. Я думал о том, чтобы включить его прямо в язык.

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

Это означает, что операция TypeOf вернет любой нужный вам тип; не навязывать вам нить. В одном контексте вы можете использовать его как проверку, чтобы спросить, является ли что-то разрешенным типом, и вернуть логическое значение. В другом контексте он может возвращать строку для отображения или число, которое будет использовано внешней программой.

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

Я был удивлен и доволен.

SQL: возвращение

Из всего кода Флаббергаста мне больше всего нравится библиотека SQL. Это началось как забава, чтобы иметь возможность потреблять информацию из базы данных во Флаббергасте, но обрело собственную жизнь. Больше всего в реляционных базах данных разочаровывает то, что их синтаксис немного отличается в разных базах данных. Поскольку поиск можно использовать для изменения шаблонов, можно создать объявление для запроса, установить атрибут для хранения реализаций всех шаблонов для конкретного поставщика базы данных, а затем подставить правильный синтаксис базы данных с помощью поиска. Это не очень впечатляет, поскольку подобные вещи являются довольно стандартной объектно-ориентированной практикой, но Флаббергаст идет дальше.

Поскольку поиск находит реализации, используя контекстный поиск, в отличие от объектно-ориентированного языка, нам не нужна фабрика для создания частей запроса. Фабрика может быть неявно предоставлена ​​поиском, и мы можем определить область действия фабрики.

Также можно составить из шаблонов полезные подвыражения SQL. В самой библиотеке SQL это происходит редко — пользователи этой библиотеки будут делать такую ​​композицию. Тем не менее, такой тип композиции присутствует в компиляторе: шаблоны составляются для создания новых шаблонов таким образом, чтобы они выглядели неотличимыми от «базового» шаблона. Например, есть шаблон для разбора идентификатора, который выглядит как примитивная операция разбора, но представляет собой сложную композицию. Если бы вы застряли на фабричной земле, вам пришлось бы создать статический метод, который взял бы фабрику и создал композицию из объектов, созданных фабрикой, или создать оболочку фабрики, которая добавила бы некоторые новые методы (и не отставала бы от меняющегося API). ). Для вида композиции во Флаббергасте имеют значение только те части интерфейса, которые непосредственно требуются композицией.

В библиотеке SQL есть все правила преобразования типов, поэтому она может проверять тип SQL-запроса перед его выполнением. Он также выполняет проверки агрегирования, чтобы убедиться, что предложения GROUP BY верны.

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

Он даже поддерживает перегрузку операторов. Арифметические выражения могут найти правило перегрузки для аргументов типа и предпринять соответствующие действия. В SQL есть типы времени и интервала, которые ведут себя по-разному в разных базах данных, но библиотека может скрыть эти различия. В SQLite нет типа интервала, но есть функции для добавления заданного количества секунд ко времени. Flabbergast может сделать вид, что интервал существует, проверить правильность выражения, а затем вызвать соответствующие функции манипулирования датами SQLite.

Побег

Flabbergast также может правильно цитировать базу данных. Хотя драйвер JDBC/ADO.NET может правильно заключать строки в кавычки, это работает только с действующей базой данных. С помощью Flabbergast можно сгенерировать правильно цитируемый запрос и вставить его в сценарий оболочки, исходный код Python, YAML, XML или JSON, и вы можете вкладывать любые из них друг в друга. На самом деле, Flabbergast имеет очень сложный механизм цитирования, который может правильно цитировать вещи через множество уровней вложенности.

Цитирование/экранирование — это общая система правил манипулирования текстом. Он указывает, что делать с определенными символами или диапазонами символов. Это позволяет вам написать правило для преобразования непечатаемых символов в шестнадцатеричные escape-последовательности или поставить обратную косую черту перед определенным символом.

Существует две реализации: одна выполняет цитирование в программе Flabbergast, а другая компилирует правила цитирования в сценарий AWK, чтобы их можно было использовать в сценарии оболочки. Как и при выборе другого поставщика базы данных, легко указать правила один раз и поменять местами в любой реализации.

Вывод

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

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

Новые языки программирования кажутся все более похожими на старые. Java, C#, Go, Python, Ruby, Swift, Kotlin, Scala, JavaScript, CoffeeScript, TypeScript, Dart и Visual Basic — все они движутся в одном направлении. Какими бы разными ни были эти языки, все они стремятся к одному и тому же основанному на классах, объектно-ориентированному языку с единой диспетчеризацией, лексической областью действия, строго типизированным языком с управлением памятью с лямбда-выражениями и синтаксическим сахаром для создания словарей и списки. Да, есть некоторые различия в статической и динамической типизации, дженериках и перегрузке операторов, но ничего шокирующего. Я не думаю, что с этой категорией языка что-то не так, но становится все труднее оправдать переход на все меньшие дополнительные выгоды.

Есть лагерь LISP, ML, OCaml, Clojure, Scheme, Haskell и F#, который пытается достичь островка чисто математических намерений, что мне нравится, но также становится однородным. C, shell и PERL на самом деле больше не разрабатываются как языки, хотя они важны и вездесущи — они стали живыми ископаемыми. C++ будет продолжать поглощать все функции любого другого языка, которые могут быть реализованы в шаблонах.

Я хочу что-то, что заставит меня думать о решении проблем по-новому.

Rust нов и интересен. Система типов Rust — это действительно новый взгляд на управление памятью. Это заставляет вас думать о том, что вы имеете в виду и чего вы действительно хотите. Эрланг захватывающий (к сожалению, не такой уж новый). Модель акторов, которую он использует, очень необычна и действительно полезна в определенных сценариях.

Я думаю, что Flabbergast — это что-то новое и интересное. Не захватывающий уровень Rust, но больше, чем очередная попытка заново изобрести лучшую Java или JavaScript. Мне нравится, что Flabbergast обеспечивает больше функциональности за счет меньшего количества базовых частей. У большинства языков есть словарь и список, но Флаббергаст объединяет их вместе. Я боялся, что пожалею об этом решении, но снова и снова это доказывало свою простоту, удобство, силу и выразительность. То же самое и с выбором шаблонов только вместо шаблонов и функций. Это делает меня счастливым.

Мне, уже знающему GCL, сложно представить, каково было бы изучать Flabbergast, если бы я знал только основанные на классах, объектно-ориентированные языки с единой диспетчеризацией, лексической областью видимости, строго типизированными языками с управлением памятью с лямбда-выражениями и исключения и синтаксический сахар для создания словарей и списков. Мозг Java-программиста может взорваться, или он может получить удовольствие.

Если вы хотите попробовать, вы не будете первым, кто выучит это.

Примите странное. Вы не знаете, что вы можете обнаружить.