#macros #clojure
#макросы #clojure
Вопрос:
У меня есть простой макрос:
(defmacro macrotest [coll]
`(let [result# ~(reduce coll)]
result#))
Почему, если этот код работает:
(macrotest [1 2 3])
разве этот код не работает?
(def mycoll [1 2 3])
(macrotest mycoll)
Комментарии:
1. Уменьшение происходит во время компиляции, поэтому вы пытаетесь выполнить (уменьшить somesymbol) при вызове его в mycoll. Что именно вы хотите, чтобы ваш макрос делал? Я предполагаю, что вы хотели (уменьшить ~ coll).
2. макрорасширение — ваш друг
Ответ №1:
Символы не совпадают со значением, на которое они указывают.
Одним из важных соображений, касающихся макросов, является не только то, как они создают новый код, но и то, как они обрабатывают передаваемые вами аргументы.
Рассмотрим это:
(def v [1 2 3])
(somefunction v)
Аргумент v
, переданный этой функции, не поступает внутрь функции в виде символа, v
. Вместо этого, поскольку это вызов функции, сначала вычисляются аргументы, а затем их результирующее значение передается в функцию. Итак, функция увидит [1 2 3]
.
Но макросы не такие, хотя это легко забыть. Когда вы вызываете:
(somemacro v)
v
не вычисляется и не передается [1 2 3]
. Внутри макроса все, что вы получаете, это символ, v
. Теперь ваш макрос может выдавать переданный вами символ в коде, который создает макрос, но он ничего не может сделать со значением v
, если вы не добавите дополнительный уровень оценки с помощью eval
(см. Сноску — не рекомендуется).
Когда вы снимаете кавычки с аргумента макроса, вы получаете символ, а не значение.
Рассмотрим этот пример:
user> (def a 5)
#'user/a
user> (defmacro m [x] `( ~x ~x))
#'user/m
user> (m a)
10
user> (macroexpand '(m a))
(clojure.core/ a a)
user> (defmacro m [x] `~( x x))
#'user/m
user> (m a)
ClassCastException clojure.lang.Symbol cannot be cast to java.lang.Number clojure.lang.Numbers.add (Numbers.java:126)
Это было бы похоже на попытку сделать это в REPL: ( 'a 'a)
Добавление символов, а не добавление значений, на которые указывают эти символы.
Вы можете посмотреть на эти два разных определения макроса m
и подумать, что они делают одно и то же. Но во втором есть попытка что-то сделать с символом x
, сделать с ним дополнение. В первом макросе компилятору не предлагается ничего делать с x. Ему просто приказано выполнить операцию сложения, которая затем будет фактически обработана во время выполнения.
Помните, что целью макроса является создание кода, а не выполнение действий во время выполнения, которые выполняла бы функция. Созданный код затем немедленно запускается (через неявный eval
), поэтому об этом разделении легко забыть, но это два разных шага.
В качестве последнего упражнения представьте, что вы компилятор. Я хочу, чтобы вы прямо сейчас сказали мне, каково результирующее значение этого кода:
(* t w)
Ну? Это невозможно. Вы никак не можете сказать мне ответ на этот вопрос, потому что вы понятия не имеете, что t
такое и. w
Однако позже, во время выполнения, когда они предположительно будут иметь значение, тогда это будет легко.
И это то, что делают макросы: они выводят данные для обработки во время выполнения.
(Очень не рекомендуется, но для дальнейшего описания происходящего это работает 🙂
пользователь> (определение 1)
пользователь> (defmacro m [x] `~( (eval x) (eval x)))
пользователь> (ma)
2