Преобразовать функцию в другую форму внутри макроса - PullRequest
2 голосов
/ 29 января 2012

Я пишу небольшой файл данных, немного похожий на этот

(top-section 1 "start of text"
  (link "bit of text")
  (link "bit of text 2"))

Я хочу использовать макрос для преобразования вышеупомянутой формы и обработки ее в моей системе, однако у меня возникают проблемы с попытками выяснить, как заставить часть link макроса работать должным образом. Моя функция ссылки такая:

(defn link [top-section-id link-text]
    ....)

Как вы можете видеть, это принимает два аргумента, однако мое определение выше передает только один аргумент. Я хочу «преобразовать» данные, переданные через DSL, чтобы вставить идентификатор top-section выше в функцию ссылки.

Так что на самом деле он должен преобразовать входные данные в

(top-section 1 "start of text"
  (link 1 "bit of text")
  (link 1 "bit of text 2"))

Как я могу это сделать, если читатель Clojure не оценит код и не выдаст ошибку, сообщив, что я передал только один аргумент функции link. Есть ли в любом случае "экранирование" ввода, чтобы оно не оценивалось, пока я не произвел необходимые преобразования

Я знаю, что мог бы просто сделать

(top-section 1 "start of text"
  '(link "bit of text")
  '(link "bit of text 2"))

Чтобы вернуть форму списка, но есть ли другой способ?

Ответы [ 3 ]

2 голосов
/ 30 января 2012

Вы могли бы top-section развернуть до специально созданной формы let, связывающей символ link с частичным применением исходной функции link к первому аргументу формы top-section:

(defmacro top-section [n s & forms]
  `(let [~'link (partial ~'link ~n)]
     (prn ~s) ; handle s in whichever way is appropriate
     ~@forms))

;; for the sake of example
(defn link [n s] (prn n s))

Взаимодействие REPL (напечатано три строки, nil возвращено):

user> (top-section 1 "start of text"
        (link "more text")
        (link "still more"))
"start of text"
1 "more text"
1 "still more"
nil

Если top-section s может потребоваться вложение, вы можете использовать более сложный top-section, который заботитсязахватить «область имен» link:

(defmacro top-section [n s & forms]
  (let [qlink (symbol (name (.. (resolve 'link) ns name)) "link")]
    `(let [~'link (partial ~qlink ~n)]
       (prn ~s)
       ~@forms)))

На REPL:

user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
nil

(Вполне возможно, совершенно ненужное усложнение следует - настраиваемыйвариант top-section - надеюсь, это несколько приятнее, если не полезно ...)

Кстати, у вас есть небольшой фиксированный набор функций, которые вы захотите обработать таким образом или выполнитеВы думаете, что это может расшириться / оказаться большим?В последнем случае вы могли бы top-section выполнить одно и то же для всех символов, хранящихся, например, где-нибудь в атоме:

(def top-section-syms (atom #{'link}))

(defmacro top-section [n s & forms]
  (let [nsym (gensym "n")
        qs (for [s @top-section-syms]
             [s (symbol (name (.. (resolve s) ns name)) (name s))])]
    `(let [~nsym ~n
           ~@(->> (for [[s q] qs]
                    [s `(partial ~q ~nsym)])
                  (apply concat))]
       (prn ~s)
       ~@forms)))

При REPL:

user> (swap! top-section-syms conj 'prn)
#{prn link}
user> (top-section 1 "start of text"
        (link "more text")
        (link "still more")
        (top-section 2 "inner section"
          (link "etc.")
          (prn "and another fn...")))
"start of text"
1 "more text"
1 "still more"
"inner section"
2 "etc."
2 "and another fn..."
nil

Операцияswap! ввод нового символа может быть предварительно проверен простой функцией / макросом (register-top-section-symbol?).

0 голосов
/ 30 января 2012

Вы можете попробовать это:

(defmacro transforming [& body]
  `(do ~@(map (fn xform [[f arg1 arg2 & more :as syms]]
                (if (= f 'top-section)
                  (apply list f arg1 arg2
                    (map #(if (= (first %) 'link)
                            (apply list (first %) arg1 (rest %))
                            (xform %))
                    more))
                  syms))
              body)))

А затем используйте его так:

(transforming
  (top-section 1 "start of text"
    (link "bit of text")
    (link "bit of text 2")
    (top-section 3 "nested"
      (link "nested sections should work too")))
  (top-section 2 "section two"
    (link "text")
    (link "text 2")))

Что будет расширяться до:

(do
  (top-section 1 "start of text"
    (link 1 "bit of text")
    (link 1 "bit of text 2")
    (top-section 3 "nested"
      (link 3 "nested sections should work too")))
  (top-section 2 "section two"
    (link 2 "text")
    (link 2 "text 2")))

Однако у этого макроса рекурсивный вызов, и я уверен, что его можно сделать намного красивее.

0 голосов
/ 29 января 2012

Если top-section является макросом, он получает link формы без оценки, так что он может преобразовывать их так, как ему захочется.

Я бы предложил что-то другое, но: Макрос top-section оценивать свои подчиненные формы в контексте, в котором некоторая динамическая переменная связана с каким-либо соответствующим аргументом top-section, и ссылаться на него из функции link:

(def ^:dynamic *id*)

(defmacro top-section [id text & body]
  `(binding [*id* ,id]
     ...
     ~@body))

(defun link [text]
  ... *id* ...)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...