Возможность «напрямую манипулировать абстрактным синтаксическим деревом» сама по себе не является чем-то новым, хотя это очень мало в разных языках. Например, многие языки в наши дни имеют какую-то функцию eval
, но должно быть очевидно, что это , а не манипулирование синтаксическим деревом abstract , вместо этого это манипуляция синтаксиса concrete - прямой исходный код. Кстати, упомянутая функциональность в D подпадает под ту же категорию, что и CPP: оба имеют дело с необработанным исходным текстом.
Чтобы привести пример языка, который имеет эту функцию (но не тот, который можно было бы считать собственно макросами), см. OCaml . У него есть синтаксическая система расширений CamlP4 , которая, по сути, является инструментарием расширения компилятора, и она вращается вокруг абстрактного синтаксиса OCaml как его наиболее важной цели. Но это еще не то, что делает соответствующую функцию в Лиспсе такой замечательной.
Важной особенностью Lisps является то, что расширения, которые вы получаете с помощью макросов, являются частью языка так же, как и любая другая синтаксическая форма. Иными словами, когда вы используете что-то вроде if
в Лиспе, нет никакой разницы в функциональности, независимо от того, реализовано ли это как макрос или как примитивная форма. (На самом деле есть небольшая разница: в некоторых случаях важно знать набор примитивных форм, которые не расширяются дальше.) В частности, библиотека Lisp может предоставлять простые привязки макросов и , что означает, что библиотеки могут расширять язык гораздо более интересным способом, чем обычные скучные расширения, которые вы получаете в большинстве языков, способные добавлять только простые привязки (функции и значения).
Теперь, если смотреть в этом свете, что-то вроде объекта D очень похоже на природу. Но тот факт, что он имеет дело с необработанным текстом, а не с AST, ограничивает его полезность. Если вы посмотрите на пример на этой странице,
mixin(GenStruct!("Foo", "bar"));
вы можете видеть, как это не выглядит как часть языка - чтобы сделать его более похожим на Lisp, вы бы использовали его естественным образом:
GenStruct(Foo, bar);
без необходимости в ключевом слове mixin
, которое отмечает, где используется макрос, нет необходимости в этом !
, а идентификаторы указываются как идентификаторы, а не строки. Более того, определение должно быть выражено более естественно, что-то вроде (придумав здесь какой-то неправильный синтаксис):
template expression GenStruct(identifier Name, identifier M1) {
return [[struct $Name$ { int $M1$; }; ]]
}
Одна важная вещь, которую следует здесь отметить, состоит в том, что, поскольку D является языком со статической типизацией, AST явно вползли в это умственное упражнение - как типы identifier
и expression
(я предполагаю, что template
помечает это как определение макроса, но для него все еще требуется тип возвращаемого значения).
В Лиспе вы получаете что-то очень похожее на эту функциональность, а не на плохое решение для работы со строками. Но вы получаете еще больше - Lisp преднамеренно изменяет базовый тип списка и объединяет AST с языком времени выполнения очень простым способом: AST состоит из символов и списков и других базовых литералов (чисел, строк, логических значений), и все это часть языка времени выполнения. Фактически, для этих литералов Lisp делает еще один шаг вперед и использует литералы в качестве собственного синтаксиса - например, число 123
(значение, которое существует во время выполнения) представляется синтаксисом, который также равен число 123
(но теперь это значение, которое существует во время компиляции). Суть в том, что код, связанный с макросами, в Лиспе, как правило, гораздо проще иметь дело, чем то, что другие языки называют «макросами». Представьте, например, что в примере кода D создаются поля N int
в структуре (где N - новый вход для макроса) - для этого потребуется использовать некоторую функцию для перевода строки в число.