Как применить gensym для каждой конкретной переменной
Я хочу написать макрос (my-dotimes [x init end] & body), который вычисляет значение body для x при переходе от init к end-1 с шагом 1. Здесь вы снова должны убедиться, что избегаете переменной " проблема захвата ". Это должно работать так:
user=> (my-dotimes [x 0 4] (print x))
0123nil
мой код:
(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
(when (< i# ~end)
~@body
(recur (inc i#))))))
но когда я использую macroexpand, чтобы проверить это и найти:
user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
(println x)
(recur (clojure.core/inc i__4548__auto__))))
Мне интересно как поменять
(println x) => (clojure.core/println i__4548__auto__)
1 ответ
Здесь вы предоставляете символ, который должен быть привязан к счетчику (здесь x
), поэтому вам не нужно использовать gensyms. Вместо того, чтобы использовать i#
просто введите символ, данный вам пользователем макроса. Вам нужны генсимы, когда вы вводите новые символы и не хотите, чтобы они сталкивались с существующими символами.
В Common Lisp имеет смысл обернуть тело привязкой из предоставленного пользователем символа к текущему значению i
, с помощью (let ((,x ,i)) ,@body)
потому что код пользователя может изменить значение счетчика во время итерации (что может быть плохо). Но здесь я думаю, что вы не можете изменить переменную напрямую, поэтому вам не нужно беспокоиться об этом.
Ваш второй пример:
(defmacro for-loop [[symb ini t change] & body]
`(loop [symb# ~ini]
(if ~t
~@body
(recur ~change))))
Первая проблема: когда вы расширяете тело, которое может быть одной или несколькими формами, вы получите if
форма с большим количеством ответвлений вместо 2. Например, (if test x1 x2 x3 (recur ...))
если ваше тело содержит x1
, x2
а также x3
, Вы должны обернуть тела в do
выражения, с (do ~@body)
,
Теперь ситуация не очень отличается от прежней: у вас все еще есть символ, данный пользователем, и вы несете ответственность за установление привязок в макросе. Вместо того, чтобы использовать symb#
, который создает новый символ, полностью отличающийся от symb
Просто используйте symb
непосредственно. Вы можете сделать это, например (не проверено):
(defmacro for-loop [[symb init test change] &body]
`(loop [~symb ~init]
(if ~test (do ~@body) (recur ~change))))
Пока вы используете символ, предоставленный вызывающей стороной вашего макроса, генсимы не нужны. Вам нужно использовать gensyms, когда вам нужно создать новую переменную в сгенерированном коде, которая требует наличия нового символа. Например, вы оцениваете выражение только один раз и вам нужна переменная для хранения его значения:
(defmacro dup [expr]
`(let [var# ~expr]
[var# var#]))