clojure宏生成函数

Kev*_*vin 7 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)
Run Code Online (Sandbox Code Playgroud)

我得到的错误是:

java.lang.ClassCastException: clojure.lang.Cons cannot be cast to java.lang.String
Run Code Online (Sandbox Code Playgroud)

而且我不确定这意味着什么,但我有这个模糊的概念(因为...)没有像我认为的那样在宏内部工作.

ama*_*loy 17

您对运行时和编译时之间以及宏和函数之间的区别感到困惑.与解决宏观问题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)))
Run Code Online (Sandbox Code Playgroud)

1非常非常罕见


编辑

您也可以在没有宏的情况下完全执行此操作,方法是使用函数创建函数并使用较低级别的操作intern代替def.事实证明它更简单了:

(letfn [(placeholder [n]
          (fn [& args]
            (nth args n)))]
  (doseq [i (range 5)]
    (intern *ns* (symbol (str "_" i))
            (placeholder i))))
Run Code Online (Sandbox Code Playgroud)

  • 如果你打算使用`eval`,那么就没有理由让一个宏参与其中.你可能只有一个函数将`eval`映射到它生成的一些代码上.但是......这就是编译器的作用!它需要一些输入形式并编译它们.引入`map`会产生额外的复杂性:它很懒惰.您的代码段将在REPL中工作,因为REPL会强制输出序列,但在编译完整文件时则不会.此外,'eval`有局限性:它不知道封闭词法范围,所以你不能使用闭包等.Eval是一个可怕的拐杖; 尽可能避免它. (3认同)