clojure宏生成函数

clojure宏生成函数,clojure,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))

我正在尝试编写一个宏,它将生成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

我真的不知道这意味着什么,但我有一个模糊的概念,即(for…)在宏中的工作方式与我认为的不同。

一旦
(生成占位符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“\uu”i\uuuu 1862\uuuu auto\uuuu)
不会被计算,因此会作为列表传入

我玩了一会儿,想出了这个:

(defmacro make-placeholders [n]
  `(map eval
        '~(for [cntr (range 0 n)]
           `(defn ~(symbol (str "_" cntr))
               {:placeholder true} [& args] (nth args ~cntr)))))
宏扩展
(生成占位符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))))
这正是我的意图,并对其进行了评估,定义了函数
\u 0
\u 1
\u 2

;=> (_0 1 2 3)
1
;=> (_1 1 2 3)
2
;=> (_2 1 2 3)
3
好的,这是可行的,但我仍然不确定这样做是不是一个好主意

首先。好的,也可以不使用
eval
,而是使用
do
(将我的解决方案中的
map eval
替换为
do
)。但您可能会使代码难以理解,因为您创建的函数没有在代码中的任何地方定义。我记得当我刚开始使用Clojure时,我在某个库中查找函数,但找不到它。我开始想,yikes,这家伙一定在某个地方定义了一个宏,定义了我正在寻找的函数,我怎么能理解发生了什么?如果这就是人们使用Clojure的方式,那么它将是一个地狱般的混乱,让人们对Perl所说的一切相形见绌。。。事实证明,我只是看错了版本——但你可能给自己和他人带来了一些困难


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

您对运行时和编译时以及宏和函数之间的区别感到困惑。用
eval
解决宏问题永远不是正确的答案:相反,请确保返回的代码符合您的期望。这里有一个最小的改变,使您的原始版本的工作

主要变化是:

  • defn from
    是一个函数,而不是宏-您只需要一种方便的方法来创建列表,主宏负责将列表插入结果表单中。这里不需要宏,因为不希望将其扩展到
    make placeholder
    的主体中

  • make placeholder
    do
    开头,并在语法引号之外为执行其
    。这是最重要的部分:您希望返回给用户的代码看起来像
    (do(defn…)
    ,就好像他们都是手工输入的一样,而不是
    (for…)
    ,后者只能定义一个函数


  • 1非常非常罕见


    编辑 通过使用函数创建函数,并使用较低级别的操作
    intern
    而不是
    def
    ,也可以完全不使用宏来执行此操作。事实证明,这要简单得多:

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

    您所说的可能是正确的,毕竟我对clojure/lisps还相当陌生,但我的理解是宏有利于(除其他外)代码生成。这就是我想做的。如果你看一下我想要的手写函数,它们是非常冗余的,似乎是这类东西的主要候选。这种类型的东西真的被认为不是一个好主意吗?嗯,它们(Lisp)当然擅长代码生成。我认为,如果您愿意使用eval,那么您实际上可以让它工作(仍在考虑这一点)。这可能不需要评估。但特别是在您的示例中,看起来函数唯一要做的事情就是调用(nth arg i)。这真的比直接使用该函数好吗?在函数式编程中,这些函数没有太多使用,请参见.map over eval,nice。是的,我想听听人们对此的看法。我能看出这看起来有多危险。@Kevin这是一个很好的练习;)我问这是不是个好主意。你能详细说明一下为什么这里的
    do
    eval
    好吗?在这个特定的用例中,它们似乎没有什么不同(代码仍然会在编译时被求值,对吗?)如果您要使用
    eval
    ,那么根本就没有理由使用宏。您还可以使用一个函数,将它生成的一些代码映射到
    eval
    。但是…编译器就是这么做的!它接受一些输入表单并进行编译。引入
    map
    会带来额外的复杂性:它是懒惰的。您的代码段将在REPL中工作,因为REPL强制输出序列,但在编译完整文件时不会。此外,
    eval
    也有局限性:它不知道如何封闭词法范围,所以不能使用闭包等。eval是一个可怕的拐杖;尽可能避免。
    (letfn [(placeholder [n]
              (fn [& args]
                (nth args n)))]
      (doseq [i (range 5)]
        (intern *ns* (symbol (str "_" i))
                (placeholder i))))