【问题标题】:Using macros in Clojure在 Clojure 中使用宏
【发布时间】:2011-05-08 01:19:53
【问题描述】:

我专门尝试使用 Clojure 中的 appengine-magic 为 crud 函数生成样板,以与 Google App Engine 数据存储一起使用。我很难弄清楚如何从我在下面复制的模型中生成值。

(def *model* {:users [{:name "Adam"
                       :email "adam@gmail.com"
                       :registered-on "07-05-2011"}
                      {:name "Greg"
                       :email "gregory@gmail.com"
                       :registered-on "11-05-2011"}]
              :post [{:title "A"
                      :authour "Adam"}
                     {:title "B"
                      :author "Greg"}]})

我对 appengine-magic 还很陌生,但它提供了一种防御功能,允许您定义可以放入数据存储区并保存的实体!它允许您将预定义的实体保存到数据存储中。

这些采取以下形式:

(ds/defentity Post [title author])
(ds/save! (Post. title author))

现在就从我定义的开始:

(defn list-entities [model]
  "Takes a representation of the model and lists the entities in preparation for generating defentities"
  (interleave (vec (map first (partition 1 (map (comp symbol capitalize #(str % ".") name) (keys model)))))
    (map vec (map keys (map first (vals model))))))

调用它:

(list-entities *model*)

输出:

(Users. [:name :email :registered-on] Post. [:title :author])

现在我很难定义 gen-entities,它将采用上面的输出并重复调用 ds/defentities 定义我的模型需要的尽可能多的实体。

(defmacro gen-entities [entity fields]
  `(ds/defentity 'entity 'fields))

此外,我无法确定这是解决此问题的合理方法。我对宏还是很陌生,可能会犯一些错误。任何帮助/澄清将不胜感激。

注意:

我意识到的那个模型设计得很糟糕,下面的那个要好得多:

(def *model* {:users [:name :email :registered-on]
              :post [:title :author]})

但是在编写宏方面它更复杂,所以我将保持原样。

【问题讨论】:

  • 您为什么不想创建一个函数,将输入直接转换为对 ds/defentity 的调用?您在这里需要宏的唯一原因是您的函数生成的输出。
  • 那么我可能会在这里遗漏一些东西,我已经尝试过了,但它似乎不起作用。所以我认为这是我必须使用宏的情况。我知道除非需要,否则我们不应该使用它们,所以我想我会在这里问一下。

标签: macros clojure google-cloud-datastore appengine-magic


【解决方案1】:

我认为需要一个宏,因为defentity 似乎定义了一个类型。

(defmacro gen-entities
  [model]
  `(do
     ~@(for [[entity-kw values] model]
         (let [entity-sym (-> entity-kw name capitalize symbol)
               fields     (map (comp symbol name) (keys (first values)))]
           `(ds/defentity ~entity-sym [~@fields])))))

您不必相互拨弄键和值,只需通过交错将它们重新组合在一起即可。在地图上进行映射将一次性为您提供键和相应的值。

user=> (macroexpand-1 `(gen-entities ~model))
(do
  (ds/defentity Users [name registered-on email])
  (ds/defentity Post [title authour]))

注意:这不适用于存储在 Var 中的模型。您必须在对 gen-entities 的调用中指定模型。

user=> (macroexpand-1 '(gen-entities model))
(
#<IllegalArgumentException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol>

【讨论】:

  • 我似乎遗漏了一些东西,当我按照您的显示运行 macroexpand-1 时,我得到了输出。但是,如果我尝试评估调用(gen-entities inserting-model-here)而不是对其进行宏扩展,它不起作用,给我一个 java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang .IObj [抛出的类 java.lang.RuntimeException]。知道我错过了什么吗?
  • 另外,您不能使用预定义的 Var 似乎很奇怪,这个限制有什么原因吗?
  • @toofarsideways 修复了一个愚蠢的错误。它现在应该可以工作了。它不适用于 Var,因为宏看不到 Var 的实际值,而是符号——在本例中为 model
  • 用 (eval `model) 调用 eval 怎么样?或类似的东西?或者这是一个很大的禁忌?对此感到抱歉,我只是想弄清楚什么是好的做法。
猜你喜欢
  • 1970-01-01
  • 2011-07-24
  • 2017-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-16
  • 1970-01-01
  • 2011-05-27
相关资源
最近更新 更多