Как применить 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#]))
Другие вопросы по тегам