макрос clojure для генерации функций - PullRequest
7 голосов
/ 21 октября 2011

Я пытаюсь написать макрос, который будет генерировать n функций. Вот что у меня есть:

; only defined this because if I inline this into make-placeholders
; it's unable to expand i# in  ~(symbol (str "_" i#))
(defmacro defn-from [str mdata args & body]
    `(defn ~(symbol str) ~mdata ~args ~@body))

; use list comprehension to generate n functions
(defmacro make-placeholders [n] 
    `(for [i# (range 0 ~n)] (defn-from  (str "_" i#) {:placeholder true} [& args] (nth args i#))))

; expand functions _0 ... _9
(make-placeholders 9)

Я получаю ошибку:

java.lang.ClassCastException: clojure.lang.Cons cannot be cast to java.lang.String

И я не совсем уверен, что это значит, но у меня есть смутное представление о том, что (для ...) не работает так, как я думаю, внутри макроса.

Ответы [ 2 ]

17 голосов
/ 21 октября 2011

Вы путаетесь в различиях между временем выполнения и временем компиляции, а также между макросами и функциями. Решение проблемы макроса с помощью eval никогда не является 1 правильным ответом: вместо этого убедитесь, что вы возвращаете код, который выполняет то, что вы хотите. Вот минимальное изменение, чтобы ваша оригинальная версия работала.

Основные изменения:

  1. defn-from - это функция, а не макрос - вам просто нужен удобный способ создания списков, который основной макрос отвечает за вставку в форму результата. Вы не хотите здесь макрос, потому что вы не хотите, чтобы он был расширен в тело make-placeholders.

  2. make-placeholders начинается с do и делает его for вне синтаксической кавычки. Это самая важная часть: вы хотите, чтобы код, возвращаемый пользователю, выглядел как (do (defn ...)), как если бы он набрал все вручную - , а не (for ...), который мог только когда-либо определить одна функция.


(defn defn-from [str mdata args & body]
    `(defn ~(symbol str) ~mdata ~args ~@body))

; use list comprehension to generate n functions
(defmacro make-placeholders [n]
  (cons `do
        (for [i (range 0 n)]
          (defn-from (str "_" i) {:placeholder true}
            '[& args]
            `(nth ~'args ~i)))))

user> (macroexpand-1 '(make-placeholders 3))
(do (clojure.core/defn _0 {:placeholder true} [& args] (clojure.core/nth args 0)) 
    (clojure.core/defn _1 {:placeholder true} [& args] (clojure.core/nth args 1)) 
    (clojure.core/defn _2 {:placeholder true} [& args] (clojure.core/nth args 2)))

1 Очень, очень редко


Редактировать

Вы также можете сделать это полностью без макросов, используя функцию для создания функций и используя операцию более низкого уровня intern вместо def. На самом деле все оказывается гораздо проще:

(letfn [(placeholder [n]
          (fn [& args]
            (nth args n)))]
  (doseq [i (range 5)]
    (intern *ns* (symbol (str "_" i))
            (placeholder i))))
3 голосов
/ 21 октября 2011

Макроэкспонирование после выражения (make-placeholders 9) Я получаю это:

(for
 [i__1862__auto__ (range 0 9)]
 (defn-from
     (str "_" i__1862__auto__)
     {:placeholder true}
   [& args]
   (nth args i__1862__auto__)))

Так что defn-from ожидает строку в качестве первого аргумента, но, поскольку это макрос, (str "_" i__1862__auto__) не оценивается и, таким образом, вставляется в список.

Я какое-то время играл с этим и придумал:

(defmacro make-placeholders [n]
  `(map eval
        '~(for [cntr (range 0 n)]
           `(defn ~(symbol (str "_" cntr))
               {:placeholder true} [& args] (nth args ~cntr)))))

Макроэкспандирование (make-placeholders 3) дает

(map eval
 '((defn _0 {:placeholder true} [& args] (nth args 0))
   (defn _1 {:placeholder true} [& args] (nth args 1))
   (defn _2 {:placeholder true} [& args] (nth args 2))))

то, что я намеревался и оцениваю, определяет функции _0, _1 и _2:

;=> (_0 1 2 3)
1
;=> (_1 1 2 3)
2
;=> (_2 1 2 3)
3

Хорошо, это работает, но я все еще не уверен, что это хорошая идея.

Прежде всего Eval is evil . Хорошо, это также можно сделать без eval, но с использованием do (замените map eval в моем решении на do). Но вы, возможно, делаете свой код трудным для понимания, потому что вы создаете функции, которые не определены нигде в вашем коде. Я помню, что когда я только начал использовать Clojure, я искал какую-то библиотеку для функции и не мог ее найти. Я начал думать yikes, этот парень, должно быть, где-то определил макрос, который определяет функцию, которую я ищу, как я когда-нибудь пойму, что происходит? Если это то, как люди используют Clojure, то это будет чертовски бесполезно и затмит все, что люди говорят о Perl ... Оказалось, что я просто смотрю не на ту версию - но вы, возможно, настраиваете Вы и другие готовы к трудностям.

Сказав это, могут быть соответствующие применения для этого. Или вы можете использовать нечто подобное для генерации кода и поместить его в какой-то файл (тогда исходный код будет доступен для проверки). Может быть, кто-то более опытный может подключиться?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...