Не так, как вы, вероятно, ожидаете
Чтобы понять почему, вот способ мышления о макросах: макрос - это функция, которая берет немного исходного кода и превращает его в другой кусочек исходного кода. : расширение макроса. Другими словами, макрос - это функция, область и диапазон которой исходный код .
Как только исходный код полностью раскрыт, он передается либо в оценщик, либо в компилятор. Давайте предположим, что он передается компилятору, потому что он облегчает ответ на вопрос: сам компилятор - это просто функция, домен которой является исходным кодом, а диапазон - это некоторая последовательность инструкций для машины (которая может быть или не быть реальной машиной) выполнить. Эти инструкции могут включать такие вещи, как «вызов этой функции для этих аргументов».
Итак, вы спрашиваете: может ли «this function» в «вызывать эту функцию для этих аргументов» быть каким-то макросом? Ну, да, это может быть, но любой исходный код, который он собирается преобразовать, безусловно, не может быть исходным кодом программы, которую вы выполняете, потому что этого уже нет: все, что осталось, это последовательность инструкций, которая была возвращаемым значением компилятор.
Итак, вы можете сказать: ОК, допустим, мы запрещаем компиляторы: можем ли мы сделать это сейчас? Ну, если оставить в стороне то, что «запрещение компиляторов» является своего рода серьезным ограничением, это было, на самом деле, то, что делали очень старые диалекты в Lisp, используя конструкцию, называемую FEXPR , как упомянуто в другой ответ. Важно понимать, что FEXPR существовали, потому что люди еще не изобрели макросы . Довольно скоро люди изобрели макросы, и хотя FEXPR и макросы некоторое время сосуществовали - в основном потому, что люди писали код, использующий FEXPR, который они хотели продолжать выполнять, и потому что написание макросов было серьезной болью до появления таких вещей, как обратная цитата - FEXPR умерли вне. И они вымерли, потому что были семантически ужасными: даже по стандартам Лиспов 1960-х годов они были семантически ужасными.
Вот один небольшой пример того, почему FEXPR такие ужасные: допустим, я пишу эту функцию на языке с FEXPR :
(define (foo f g x)
(apply f (g x)))
Теперь: что происходит, когда я звоню foo
? В частности, что произойдет, если f
может быть FEXPR? . Ну, ответ в том, что я вообще не могу скомпилировать foo
: мне нужно подождать до времени выполнения и принять какое-то решение на лету о том, что делать.
Конечно, это не так. То, что, вероятно, делали эти старые Лиспы с FEXPR: они просто молча предположили бы, что f
была нормальной функцией (которую они назвали бы EXPR), и скомпилировали бы соответственно (и да, даже у очень старых Лиспов были компиляторы). Если вы передали что-то, что было FEXPR, вы только что потеряли: либо вещь обнаружила это, либо, скорее всего, она упала ужасно, либо дала вам какой-то ненужный ответ.
И этот вид ужаса - то, почему были изобретены макросы: макросы обеспечить семантически разумный подход к обработке кода на Лиспе, который позволяет (в конце концов, на самом деле это заняло много времени) незначительные детали, такие как компиляция, возможная вообще, код, имеющий разумную семантику, и скомпилированный код, имеющий ту же семантику, что и интерпретируемый код. Оказывается, это те функции, которые нравятся людям в их языках.
Кстати, и в Racket, и в Common Lisp макросы явно являются функциями. В Racket это функции, которые работают со специальными объектами «синтаксиса», потому что так вы получаете гигиену, но в Common Lisp, который намного менее гигиеничен c, это просто функции, которые работают с исходным кодом CL, где исходный код просто состоит из списков, символов & c.
Вот пример этого в Racket:
> (define foo (syntax-rules ()
[(_ x) x]))
> foo
#<procedure:foo>
OK, foo
теперь просто обычная функция. Но это функция, домен и диапазон которой являются исходным кодом Racket: она ожидает синтаксический объект в качестве аргумента и возвращает другой:
> (foo 1)
; ?: bad syntax
; in: 1
; [,bt for context]
Это потому, что 1
не является синтаксическим объектом.
> (foo #'(x 1))
#<syntax:readline-input:5:10 1>
> (syntax-e (foo #'(x 1)))
1
А в CL это еще проще увидеть: вот определение макроса:
(defmacro foo (form) form)
И теперь я могу взять функцию макроса и вызвать ее из некоторого исходного кода CL:
> (macro-function 'foo)
#<Function foo 4060000B6C>
> (funcall (macro-function 'foo) '(x 1) nil)
1
И в Racket, и в CL макросы фактически являются первоклассными (или, как мне кажется, в случае с Racket: почти первоклассными): это функции, которые работают с исходным кодом, который сам по себе Первый класс: вы можете писать программы на Racket и CL, которые конструируют и манипулируют исходным кодом произвольным образом: вот что макросы являются на этих языках.
В случае с Racket я сказал: почти в первом классе », потому что я не вижу способа в Racket получить функцию, которая находится за макросом, определенным с помощью define-syntax
& c.