Clojure中常见宏错误的解决方法

发布于 2024-11-06 11:24:55 字数 2523 浏览 0 评论 0原文

基本上我对宏相当陌生,我正在尝试弄清楚如何在 Clojure 中编写宏。问题是我不断收到异常错误,并且很难弄清楚如何继续。所以我真正想知道的是我是否可以获得用于调试 Clojure 宏的任一方法(或启发式)的列表。

以我正在处理的当前问题为例,我有这个模型:

{:users [:name :email :registered-on]
 :post [:title :author]}

我想将其转换为以下形式:

(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users"
         ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) 
    (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post"
         ["title" "author"] ("Title" "Author"))))

我为此编写了这个宏:

(defmacro gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

但是,当我运行宏时,我得到:

java.lang.String cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]

我尝试调用 Macroexpand-1,但我遇到了同样的错误,让我不知道如何解决它。

本教程由 John Lawrence Aspden 提供他说:“当编译器看到一个宏(它只是一个返回一些代码的函数)时,它会运行该函数,并替换返回到程序中的代码。”促使我将宏编写为函数,该函数接受我拥有的值并输出我希望它们转换成的结果。

因此,这是有效的:

(defn gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

(gen-create-forms {:users [:name :email :registered-on]
              :post [:title :author]})
(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users" ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post" ["title" "author"] ("Title" "Author"))))

我不确定我在这里是否正确使用了宏,或者宏是否是解决此问题的正确策略。但是,关于在编写宏时遇到异常时该怎么做的一些想法或者调试它们的好的技术将不胜感激。

编辑: mikera 引起我的注意,这不是一个很好的例子,但我的问题仍然存在。因此,重申一下如果在 clojure 中编码宏时遇到异常,您会使用什么技术?

Basically I'm rather new to macros and I'm trying to work out how to write macros in Clojure. The problem is I keep getting exception errors and it's very difficult to figure out where to proceed. So really what I'm wondering is if I can get a list of either methods (or heuristics) for debugging Clojure macros.

Take the current problem I am working on, I have this model:

{:users [:name :email :registered-on]
 :post [:title :author]}

and I want to convert it into the form:

(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users"
         ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) 
    (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post"
         ["title" "author"] ("Title" "Author"))))

for which I have written this macro:

(defmacro gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

However when I run the macro I get:

java.lang.String cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]

I've tried calling macroexpand-1, but I get the same error leaving me with little idea on how to solve it.

This tutorial provided by John Lawrence Aspden where he says "When the compiler sees a macro, which is just a function that returns some code, it runs the function, and substitutes the code that is returned into the program." prompted me to write the macro as a function which takes in the values I have and outputs the result that I want them to transform into.

Thus this works:

(defn gen-create-forms [model]
               `(do
                 ~@(for [[entity-kw values] model]
                        (let [entity-sym (-> entity-kw name capitalize)
                             fields (vec (map name values))]
                          `(def ~(symbol (str "new-form-" (name entity-kw))) (new-form ~(str "/" (name entity-kw) "/new") ~(str "Create a new " entity-sym) ~fields ~(map capitalize fields)))))))

(gen-create-forms {:users [:name :email :registered-on]
              :post [:title :author]})
(do (def new-form-users (cashew.core/new-form "/users/new" "Create a new Users" ["name" "email" "registered-on"] ("Name" "Email" "Registered-on"))) (def new-form-post (cashew.core/new-form "/post/new" "Create a new Post" ["title" "author"] ("Title" "Author"))))

I'm not sure if I am using macros correctly here or if macros are the right strategy for solving this problem. However some ideas about what you do when faced with exceptions when writing a macro or good techniques for debugging them would be much appreciated.

EDIT: It has been brought to my attention by mikera that this is not a good example, however my question still stands. So to reiterate what techniques would you use when faced with an exception if coding a macro in clojure?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

○闲身 2024-11-13 11:24:55

就我个人而言,我不会为此使用宏 - 我建议的替代方案是:

  • 避免尝试在名称空间中生成新的符号名称 - 这可能会变得混乱和复杂!
  • 相反,创建一个名为“new-forms”的单一数据结构,其中包含哈希图中的所有表单。您可以使用关键字或字符串作为键,我个人会使用“:users”之类的关键字,因为您已经在模型数据结构中使用了这种方法。
  • 使用以模型为参数的函数生成“new-forms”,并根据需要为模型中的每个表单调用 (cashew.core/new-form ... )
  • 当您想要访问时对于特定的表单,您只需执行 (new-forms :users) 或类似操作即可从哈希映射中读取适当的表单。

Personally I wouldn't use a macro for this - my proposed alternative would be:

  • Avoid trying to generate new symbol names in the namespace - this can get messy and complicated!
  • Instead create a single data structure called "new-forms" that contains all the forms in a hashmap. You could use either keywords or strings as keys, personally I'd use keywords like ":users" since you are already using that approach for your model data structure.
  • Generate "new-forms" with a function that takes the model as a parameter and calls (cashew.core/new-form ... ) for each form in the model as required
  • When you want to acess a specific form, you can then just do (new-forms :users) or similar to read the appropriate form out of the hashmap.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文