[Перевод] Об изучении компиляторов и создании языков программирования
Когда я только начинал изучать компиляторы, я не понимал одного важного момента: Разработка языка программирования и написание компилятора — это два почти совершенно разных навыка. Конечно, между ними есть некоторое пересечение, но меньшее, чем может показаться на первый взгляд!
Я думаю, что это важно знать, потому что обе эти задачи очень сложны! Легче овладеть этими навыками, если решать их по очереди. Ваше первое решение сложной задачи, скорее всего, будет… ну, не таким хорошим, как десятое! Однако довольно часто разработчики создают игрушечный язык, когда пишут свой первый компилятор, а это значит, что они совершают ошибки и учатся сразу на двух сложных проблемах. Хуже того, эти две проблемы взаимосвязаны: Ошибка в одной из них усложняет жизнь в другой. Кроме того, как мы увидим, хотя компиляторы и сложны на первых порах, они, по сути, являются решенной проблемой, в то время как разработка языка программирования — это, скажем так, область продолжающихся исследований.
Я думаю, что проще сначала научиться писать компилятор, а потом (если захочется) заняться разработкой языков программирования. Научиться разрабатывать хороший язык программирования будет сложнее, если у вас нет опыта решения задач разработки компиляторов (помимо всего прочего), поэтому, если вы хотите освоить оба навыка, я рекомендую делать это в таком порядке: компилятор, затем язык программирования. Создание компиляторов полезно практически всем инженерам-программистам, даже (особенно?) тем, кто не будет создавать компиляторы в рамках своей основной работы. Знание сложностей разработки языка программирования также пригодится, но, как мне кажется, в более абстрактном виде.
Сложности в разработке компиляторов
Когда я говорю, что разработка компиляторов — это «решенная проблема», я не имею в виду, что в этой области больше никогда не будет инноваций. Напротив, я имею в виду, что существуют опубликованные решения всех этих проблем, и вы можете просто найти их и использовать/извлечь из них уроки. Кроме того, разработка компилятора — это в основном техническая проблема. Хотя, как и в любой другой области программной инженерии, необходимо учитывать человеческий фактор («Какие сообщения об ошибках будут наиболее полезны в данной ситуации, и как я могу быть уверен, что выдаю именно такие сообщения?»), многие проблемы, которые вы будете решать, будут сугубо техническими («Когда пользователь пытается скомпилировать большой файл, будет ли это работать, или компилятор станет очень медленным/закончится память?»).
Конкретные решения, которые необходимо будет принять при разработке, включают следующее:
• Обработка ошибок («Когда я сталкиваюсь с неполным или ошибочным кодом, я хотел бы выдавать пользователю полезное сообщение на каждую ошибку в программе, а не сразу выводить бесполезное сообщение при первой же ошибке»).
• Проблемы синтаксиса («Когда вы встречаете знак »-», это унарное отрицание или знак минус?»)
• Проблемы семантики («Могу ли я правильно разрешить перегрузку функций в соответствии со спецификацией языка программирования для каждого запроса?»)
• Проблемы определения типов («Определяет ли компилятор корректность типа с помощью вывода типа, проверки типа или «и того, и другого»?»)
Некоторые вещи, которые делают один компилятор лучше другого, вполне поддаются измерению, например, скорость. Другие, например, ясность сообщений об ошибках, измерить несколько сложнее, но, тем не менее, их можно сравнивать.
Сложности в разработке языков программирования
Когда я говорю, что разработка языка программирования является во многом нерешенной проблемой, я имею в виду две вещи.
Во-первых, хотя для некоторых из этих проблем существуют опубликованные решения (например, K Framework или PLT Redex), они не обязательно используются всеми разработчиками. Очень часто разработчики языка программирования просто не используют их. Это в меньшей степени относится к разработчикам компиляторов, которые, как правило, используют готовые паттерны, а то и библиотеки. Иногда разработчик языка программирования тщательно строит формальное исчисление языка, используя другую систему, в то время как другие разработчики просто «берутся за ум». Использование «неформального» процесса проектирования может привести к катастрофическим упущениям в разработке языка программирования, например, к ошибкам Shellshock. Однако такие «неформальные» языки, как bash и PHP, по многим показателям оказались невероятно успешными. Я здесь не для того, чтобы бросить на них тень. Я просто хочу сказать, что разработка языка программирования сложна с точки зрения создания безопасного, удобного и популярного языка программирования. Использование формальной модели, такой как K Framework или PLT Redex, может сделать ваш язык более безопасным, но само по себе не сделает его более или менее популярным. Для популярности не существует формального решения, которое я могу себе представить.
Во-вторых, неясно, что делает один язык программирования более удобным для использования, нежели другой (хотя некоторые исследования проводились). Не совсем понятно, что вообще означает понятие «удобство использования», поскольку многие критерии выглядят противоречиво («Прост ли язык программирования для начинающих и не ограничивает ли он создание некорректных программ?») В этом пространстве разработок еще много места для инноваций и открытий.
Конкретные решения по разработке, в частности, затрагивают такие вопросы:
• Проблемы с синтаксисом («Как я хочу, чтобы выглядел мой язык программирования? Какие идентификаторы/символы он должен использовать?») Люди, начинающие разработку языка программирования, часто тратят много времени на обдумывание таких вопросов, но я просто скажу, что впереди их ждет гораздо больше!
• Проблемы семантики («Если функция перегружена и имеет аргументы по умолчанию, какую версию должен вызывать вызов этой функции с заданным списком аргументов?»)
• Вопросы типизации («Следует ли использовать постепенную типизацию? Уточняющие типы, зависимые типы и т.д.?»)
• Обработка ошибок («Было бы хорошо разработать язык таким образом, чтобы устранение ошибок было проще для исполнителей»)
Для некоторых из них существуют стандартные формальные решения. Например, на проблемы синтаксиса можно решить с помощью формальной грамматики, такой как EBNF.
В одном из известных мне популярных языков синтаксис, похожий на Java, был навязан разработчику руководством на поздних стадиях разработки языка. Готовый рецепт катастрофы. Однако он все равно стал довольно популярным. Похоже, что разработка языка представляет собой примерно равное сочетание понимания формализмов, тонкого искусства и удачи. Это не так уж сильно отличается от других областей компьютерного программирования, просто это более экстремальный случай такого раздвоения навыков.
Что же вы рекомендуете?
Я бы рекомендовал начинать свой первый компилятор с «игрушечного» языка. Игрушечные языки специально упрощены, в них часто мало типов (всего один), а более сложные для реализации функции либо вообще отсутствуют, либо вводятся понемногу. Они предназначены для того, чтобы быстро освоить рабочий компилятор.
Вот несколько игрушечных языков, которые можно использовать. Этот список не является исчерпывающим или даже выборочным; скорее, я просто хочу дать вам понять, что существует множество языков, из которых можно выбирать. Выберите любой из них (или любой другой заранее разработанный игрушечный язык), и я гарантирую, что ваша первая реализация компилятора будет более успешной, чем если бы вы просто придумывали что-то на ходу!
В книге Джереми Сика «Основы компиляции» описана серия Lisp, которая по мере развития книги становится все более полнофункциональной «Зоопарк языков программирования» Андрея Бауэра и Матии Претнара представляет собой «коллекцию миниатюрных языков программирования, демонстрирующую различные концепции и приемы, используемые при разработке и реализации языков программирования». В ней представлены языки различных стилей, а также реализации на языке OCaml.
Некоторые из этих языков покажутся довольно простыми! В них могут отсутствовать нужные вам функции, такие как родовая дисперсия, монады и т.д. Это сделано специально! Если вы захотите добавить эти возможности после реализации языков, пожалуйста, попробуйте это сделать. Но лучше сначала разобраться с простыми вещами.
Некоторые книги, на которые можно ориентироваться, если вы хотите прочитать формальные инструкции при создании своего первого компилятора:
Нет такой книги, которая бы подходила всем. Вот некоторые из них, которые мне понравились.
Люди склонны рекомендовать «Книгу с драконом», особенно те, кто ее не читал. Я читал ее и не рекомендую ее при внедрении вашего первого компилятора, потому что я хочу, чтобы вы добились успеха и получили удовольствие от путешествия. Книгу Dragon Book лучше читать, если вы уже знаете все, что в ней написано. Я не думаю, что это ужасная книга, но она не должна быть первой книгой, которую вы читаете по этой теме.
Книги/ресурсы по языковой разработке? Книги/ресурсы по языковой разработке?
Это сложно. Это не мой личный интерес, поэтому я не так много читал в этой области. Есть ошибка, которую я вижу, как разработчики совершают снова и снова, которая связана с заблуждением «я изобрету игрушечный язык, пока соберу свой первый компилятор», а именно: изобрести язык программирования и затем ожидать, что мир будет прокладывать путь к вашей двери, чтобы использовать его. Есть разница между тем, чтобы заставить язык программирования компилировать игрушечные примеры, и тем, чтобы создать нечто, способное легко и эффективно справиться с любым случаем, который только может прийти в голову программисту.
Это сложно. Это не мой личный интерес, поэтому я не так много читал в этой области. Есть ошибка, которую я вижу, как разработчики совершают снова и снова, которая связана с заблуждением «я изобрету игрушечный язык, пока соберу свой первый компилятор», а именно: изобрести язык программирования и затем ожидать, что мир будет прокладывать путь к вашей двери, чтобы использовать его. Есть разница между тем, чтобы заставить язык программирования компилировать игрушечные примеры, и тем, чтобы создать нечто, способное легко и эффективно справиться с любым случаем, который только может прийти в голову программисту.
Когда вы пишете язык программирования, вы должны задать его таким образом, чтобы любая программа, которую кто-то может написать (даже, скажем, метод Даффа), приводила к разумным результатам. Если вы этого не сделаете, то вы создадите возможность доступа к ошибкам Shellshock. Это сложно! А создать язык, который не только обрабатывает произвольный ввод таким образом, чтобы не удивить пользователя, но и является достаточно интересным и уникальным с эстетической точки зрения, чтобы людям было интересно с ним ознакомиться, для меня граничит с мистикой.
Существует большой скачок в сложности между более простыми книгами, в которых рассказывается об основах определенных парадигм, таких как
• Языки программирования: Применение и интерпретация
• Прагматика языка программирования
… и более сложные, объясняющие формальности теории языков программирования, такие как
• Бенджамин Пирс «Типы в языках программирования» (однако, это полностью заслуживает внимания)
• Проектирование семантики с помощью PLT Redex
… в которых вы узнаете о том, как работают языки программирования на семантическом уровне.
Одна из недавних работ, которая является хорошим мостом между этими мирами, — «Семантика языков программирования: Это просто как 1,2,3», написанная Грэмом Хаттоном. Я бы не назвал ее базовой для чтения, но она находится между двумя описанными выше мирами.
Возможно, все это кажется слишком сложным, если вы просто хотите создать DSL для своей текстовой приключенческой игры? Обратите внимание на книгу «Создание языков на языке Racket», написанную Мэтью Флаттом.
Заключение
Я понимаю, что в ходе обсуждения были затронуты довольно глубокие отраслевые вопросы, но пусть это вас не отталкивает. Я уверен, что если вы последуете одной из перечисленных мною книг или учебников, то сможете создать работающий компилятор. В процессе работы вы узнаете много нового, и это принесет пользу всем программам, которые вы пишете, даже если они не связаны (очевидно!) с созданием языка программирования. Играйте!
p/s идет Черная пятница в издательстве «Питер»