Как правильно использовать синтаксис — кавычки и кавычки внутри `defmacro`

#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