【问题标题】:clojure macro to generate functionsclojure 宏生成函数
【发布时间】:2011-12-12 17:36:38
【问题描述】:

我正在尝试编写一个将生成 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 ...) 并没有像我认为的那样在宏中工作。

【问题讨论】:

    标签: clojure


    【解决方案1】:

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

    主要变化有:

    1. defn-from 是一个函数,而不是宏 - 您只需要一种方便的方法来创建列表,主宏负责将其插入结果表单。您确实在此处需要宏,因为您不希望它扩展到make-placeholders 的正文中。

    2. make-placeholdersdo 开头,并将其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))))
    

    【讨论】:

    • 您能否详细说明为什么do 在这里比eval 更好?在这个特定的用例中,它们似乎并没有那么不同(代码在编译时仍然会被评估,对吧?)
    • 如果您要使用eval,则完全没有理由涉及宏。您还不如拥有一个将eval 映射到它生成的一些代码的函数。但是......这就是编译器所做的!它需要一些输入表单并编译它们。引入map 会带来额外的复杂性:它很懒惰。您的 sn-p 将在 REPL 中工作,因为 REPL 强制输出序列,但在编译完整文件时不会。此外,eval 有局限性:它不知道封闭词法范围,所以你不能使用闭包等。 Eval 是一个糟糕的拐杖;尽可能避免。
    【解决方案2】:

    (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 时,我在某个库中寻找一个函数,但我找不到它。我开始想哎呀,这家伙一定在某个地方定义了一个宏来定义我正在寻找的函数,我怎么会理解发生了什么?如果这就是人们使用 Clojure 的方式,那么它将变得一团糟,而且人们所说的关于 Perl 的一切都相形见绌......原来我只是在看错误的版本 - 但你可能是让自己和他人承受一些困难。

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

    【讨论】:

    • 你说的可能是正确的,毕竟我对 clojure/lisps 还是很陌生,但我的理解是宏对(除其他外)代码生成有好处。这就是我想要做的。如果您查看我想要的手写函数,它们是非常多余的,并且似乎是此类事物的主要候选者。这种事情真的被认为不是一个好主意吗?
    • 嗯,他们(Lisps)当然擅长代码生成。我认为如果你愿意使用 eval 那么你实际上可以让它工作(仍在考虑这个)。没有评估可能是可能的。但尤其是在您的示例中,函数看起来唯一要做的就是调用 (nth arg i)。这真的比直接使用该功能更好吗?在函数式编程中,这些函数的使用并不多,请参阅here
    • map over eval,很好。是的,我想听听人们对此有何看法。我可以看到它看起来很冒险。
    • @Kevin 这是一个很好的练习;)我问这是不是个好主意here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 2011-07-28
    • 2011-08-09
    相关资源
    最近更新 更多