Как избежать анафорического макроса в Clojure? - PullRequest
9 голосов
/ 19 марта 2012

Возьмите этот (упрощенный) пример:

(defmacro make [v & body]
  `(let [~'nv ~(some-calc v)]
     ~(map #(if (= % :value) 'nv %) body)))

Прямо сейчас символ nv жестко закодирован.Есть ли способ как-то использовать gensym nv и все еще использовать его в функции карты?

Кстати, это на самом деле анафорический макрос?

Ответы [ 4 ]

4 голосов
/ 19 марта 2012

Ответ содержится в вопросе: просто используйте gensym, как если бы у Clojure не было автогензимов.

(defmacro make [v & body]
  (let [value-sym (gensym)]
    `(let [~value-sym ~(some-calc v)]
       ~@(replace {:value value-sym} body))))

Обратите внимание, что я не уверен, действительно ли вы хотите ~ или ~@ здесь - это зависит от того, предполагается ли body последовательность выражений для выполнения в let или последовательность аргументов для одного вызова функции.Но ~@ было бы намного более интуитивным / нормальным, так что я собираюсь догадаться.

Является ли этот макрос анафорическим, немного сомнительно: однозначно введение nv в область вызова было,но это было в основном непреднамеренно, поэтому я бы сказал нет.В моей пересмотренной версии мы больше не представляем nv или что-то подобное, но мы «волшебным образом» заменяем :value на v.Мы делаем это только на самом верхнем уровне тела, так что это не похоже на введение реальной области видимости - я бы сказал, что это больше похоже на неожиданное прерывание кода клиента в угловых случаях.

Для примеракак может появиться это обманчивое поведение, представьте, что одним из элементов body является (inc :value).Он не будет заменен макросом и расширится до (inc :value), что никогда не удастся.Поэтому вместо этого я бы порекомендовал анафорический макрос real , который вводит реальную область действия для символа.Что-то вроде

(defmacro make [v & body]
  `(let [~'the-value ~(some-calc v)]
     ~@body))

И тогда вызывающая сторона может просто использовать the-value в своем коде, и она ведет себя так же, как реальное, регулярное локальное связывание: ваш макрос вводит его по волшебству, но не имеетлюбые другие специальные трюки.

3 голосов
/ 19 марта 2012

На самом деле это не анафорический макрос, насколько я понимаю.

Анафорный эквивалент мог бы дать вам такой синтаксис:

(make foo 1 2 3 4 it 6 7 it 8 9)

т.е. символ it был определен такчто его можно использовать внутри тела макроса make.

Я точно не уверен, что это именно то, что вам нужно, потому что у меня недостаточно контекста о том, как этот макрос будет использоваться, ноВы можете реализовать описанное выше, например:

(defmacro make [v & body]
   `(let [~'it ~v]
      (list ~@body)))

(make (* 10 10) 1 2 3 4 it 6 7 it 8 9)
=> (1 2 3 4 100 6 7 100 8 9)

В качестве альтернативы, если вы на самом деле не пытаетесь создать новый синтаксис и просто хотите заменить :value в некоторой коллекции, тогда вам не нужен макрос: было бы лучше просто использовать заменить :

(replace {:value (* 10 10)} [1 2 :value 3 4])
=> [1 2 100 3 4]
2 голосов
/ 12 мая 2012

Одним из подходов является использование динамического связывания.

(declare ^:dynamic *nv*)

(defmacro make [v & body]
  `(binding [*nv* ~(some-calc v)]
     ~(map #(if (= % :value) *nv* %) body)))

На практике динамические переменные сосут, когда их область слишком широка (сложнее тестировать и отлаживать программы и т. Д.), Но в подобных случаяхтам, где область действия ограничена локальным контекстом вызова, где необходим анафор, они могут быть весьма полезны.

Интересный аспект этого использования заключается в том, что он является своего рода обратной формой использования макроса дляскрыть динамическое связывание (многие в стиле with- *).В этой идиоме (которая, насколько я знаю, не так уж часто встречается) привязка используется для раскрытия чего-то скрытого макросом.

2 голосов
/ 19 марта 2012

В вашем примере, some-calc и map происходят во время макроразложения, а не во время выполнения, поэтому nv не обязательно должно быть let в любом случае.Сам макрос написан неправильно, независимо от того, что связано с захватом символов.

...