Управление генерацией символов в макросах Clojure

Я пытаюсь (в качестве упражнения для самообучения) создать макрос Clojure, который будет генерировать код для применения функции к последовательности целых чисел и суммирования результата, например

f(0) + f(1) + f(2) + f(3)

Это моя попытка:

(defmacro testsum [func n] 
  `(fn [x#] (+ ~@( map (fn [i] `(~func x#)) (range n)))))

Однако, похоже, что-то идет не так с x # gensym, и я получаю две разные версии x, и, следовательно, функция не работает:

(macroexpand '(testsum inc 3))

дает:

(fn* ([x__809__auto__] 
  (clojure.core/+ 
    (inc x__808__auto__) 
    (inc x__808__auto__) 
    (inc x__808__auto__))))

Это в значительной степени именно то, что я хочу, кроме различных версий x 809 и 808 ...

Что я делаю неправильно? Я думал, что автогенсим предназначен для создания единого уникального символа именно для этой цели? Есть ли лучший способ сделать это?


person mikera    schedule 15.05.2010    source источник


Ответы (1)


Генсимы в стиле foo# действительны только внутри синтаксической кавычки, в которой они были созданы. В вашем коде два x# создаются в разных блоках синтаксических кавычек:

(defmacro testsum [func n] 
  `(fn [x#] (+ ~@( map (fn [i] `(~func x#)) (range n)))))
  ^- s-q1      ^-unquote       ^- s-q2

Чтобы исправить это, используйте явный (gensym) вызов:

(defmacro testsum [func n]
  (let [x (gensym "x")]
    `(fn [~x] (+ ~@(map (fn [i] `(~func ~x)) (range n))))))

И расширение макроса ((macroexpand '(testsum inc 3))):

(fn* ([x4966] (clojure.core/+ (inc x4966) (inc x4966) (inc x4966))))
person Michał Marczyk    schedule 15.05.2010
comment
В качестве запоздалой мысли также можно заменить (gensym "x") на `x#, хотя я никогда не видел, чтобы кто-то делал это для генсима, созданного явно вне основной формы, генерирующей расширение. - person Michał Marczyk; 15.05.2010
comment
Потрясающий Михал - отлично работает большое спасибо! Я все еще не понимаю символы после того, как приехал из мира Java .... - person mikera; 15.05.2010