Особое мнение: гомоконичность Лиспа гораздо менее полезная вещь, чем большинство фанатов Лиспа заставили бы вас поверить.
Чтобы понимать синтаксические макросы, важно понимать компиляторы. Задача компилятора - превратить читаемый человеком код в исполняемый код. С точки зрения очень высокого уровня, у этого есть две общих фазы: синтаксический анализ и генерация кода .
Синтаксический анализ - это процесс чтения кода, интерпретации его в соответствии с набором формальных правил и преобразования его в древовидную структуру, обычно известную как AST (абстрактное синтаксическое дерево). Для всего разнообразия языков программирования это одна замечательная общность: практически каждый язык программирования общего назначения разбирается на древовидную структуру.
Генерация кода принимает AST анализатора в качестве входных данных и преобразует его в исполняемый код посредством применения формальных правил. С точки зрения производительности это гораздо более простая задача; многие высокоуровневые языковые компиляторы проводят 75% или больше своего времени на разборе.
Что нужно помнить о Лиспе, так это то, что он очень, очень старый, Среди языков программирования только FORTRAN старше, чем Lisp. Еще в те дни разбор (медленная часть компиляции) считался мрачным и загадочным искусством. В оригинальных работах Джона Маккарти по теории Лиспа (тогда, когда это была просто идея, которую он никогда не думал, что она может быть реализована как настоящий язык программирования), описывается несколько более сложный и выразительный синтаксис, чем современные «S-выражения везде и везде» обозначение Это произошло позже, когда люди пытались это осуществить. Поскольку синтаксический анализ в то время не был хорошо понят, они в основном наказывали его и сводили синтаксис к гомоиконичной древовидной структуре, чтобы сделать работу синтаксического анализатора совершенно тривиальной. Конечным результатом является то, что вы (разработчик) должны сделать много парсера Работайте для этого, записывая формальный AST прямо в ваш код. Homoiconicity не «делает макросы намного проще», а делает написание всего остального намного сложнее!
Проблема в том, что, особенно с динамической типизацией, S-выражениям очень трудно носить с собой много семантической информации. Когда весь ваш синтаксис относится к одному и тому же типу (списки списков), контекст не обеспечивает много контекста, предоставляемого синтаксисом, и поэтому система макросов имеет очень мало возможностей для работы.
Теория компиляторов прошла долгий путь с 1960-х годов, когда был изобретен Lisp, и хотя достигнутые результаты были впечатляющими для своего времени, сейчас они выглядят довольно примитивно. Для примера современной системы метапрограммирования, взгляните на (к сожалению недооцененный) язык Boo. Boo является статически типизированным, объектно-ориентированным и открытым исходным кодом, поэтому каждый узел AST имеет тип с четко определенной структурой, в которую разработчик макросов может читать код. Язык имеет относительно простой синтаксис, вдохновленный Python, с различными ключевыми словами, которые придают внутренним семантическим значениям встроенные древовидные структуры, а его метапрограммирование имеет интуитивно понятный синтаксис квазиквот для упрощения создания новых узлов AST.
Вот макрос, который я создал вчера, когда понял, что применяю один и тот же шаблон к множеству разных мест в коде GUI, где я вызываю BeginUpdate()
элемент управления UI, выполняю обновление в try
блоке и затем вызываю EndUpdate()
:
macro UIUpdate(value as Expression):
return [|
$value.BeginUpdate()
try:
$(UIUpdate.Body)
ensure:
$value.EndUpdate()
|]
На macro
самом деле команда - это сам макрос , который принимает тело макроса в качестве входных данных и генерирует класс для обработки макроса. Имя макроса используется в качестве переменной, которая MacroStatement
заменяет узел AST, представляющий вызов макроса. [| ... |] является блоком квазицитатов, генерирующим AST, который соответствует коду внутри, а внутри блока квазиквот символ $ обеспечивает функцию «unquote», подставляя в узел, как указано.
С этим можно написать:
UIUpdate myComboBox:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
и расширить его до:
myComboBox.BeginUpdate()
try:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
ensure:
myComboBox.EndUpdate()
Выражая макрос таким образом , является более простым и понятным , чем это было бы в макросе Lisp, потому что разработчик знает структуру MacroStatement
и знает , как Arguments
и Body
свойства работы, и что присущая знания могут быть использованы для выражения понятий , связанных с очень интуитивным путь. Это также безопаснее, потому что компилятор знает структуру MacroStatement
, и если вы попытаетесь закодировать что-то, что недопустимо для MacroStatement
, компилятор сразу же поймает это и сообщит об ошибке, а вы не узнаете, пока что-то не взорвется во время выполнения.
Привить макросы на Haskell, Python, Java, Scala и т. Д. Несложно, поскольку эти языки не гомоичны; это сложно, потому что языки не предназначены для них, и это работает лучше всего, когда иерархия AST вашего языка разработана с нуля для изучения и манипулирования макросистемой. Когда вы работаете с языком, который изначально разрабатывался с учетом метапрограммирования, с макросами работать намного проще и проще!