clojure宏生成函数

时间:2011-10-21 16:22:37

标签: clojure

我正在尝试编写一个会生成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 个答案:

答案 0 :(得分:17)

您对运行时和编译时之间以及宏和函数之间的区别感到困惑。使用eval解决宏问题绝不是 1 正确的答案:相反,请确保返回执行您想要发生的代码。这是使您的原始版本有效的最小变化。

主要变化是:

  1. defn-from是一个函数,而不是宏 - 您只需要一种方便的方法来创建列表,主宏负责插入结果表单。你在这里想要一个宏,因为你不希望它扩展到make-placeholders的主体。

  2. make-placeholdersdo开头,并在语法报价的<{1}} 之外。这是最重要的部分:您希望返回给用户的代码看起来像for,就像他们手动输入所有代码一样 - 不是 (do (defn ...)),这只能定义一个单一的功能。


  3. (for ...)

    1 很少,很少


    修改

    您也可以在没有宏的情况下完全执行此操作,方法是使用函数创建函数并使用较低级别的操作(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))) 而不是intern。事实证明它更简单:

    def

答案 1 :(得分:3)

一旦我得到(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)))))

Macroexpanding (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所说的一切都相形见绌... 原来我只是看错了版本 - 但你可能是为自己和其他人设置一些困难。

话虽如此,可能有适当的用途。或者您可以使用类似的东西来生成代码并将其放入某个文件中(然后源代码可供检查)。也许更有经验的人可以加入进来?