如何正确使用valip谓词gt与宏?

时间:2016-01-20 10:48:55

标签: clojure macros clojurescript

我正在使用Modern ClojureScript教程,并尝试编写一些宏来自动执行某些操作。

例如,在一个验证函数中:

(defn validate-shopping-form [quantity price tax discount]
  (validate {:quantity quantity :price price :tax tax :discount discount}

            ;; validate presence

            [:quantity present? "Quantity can't be empty"]
            [:price present? "Price can't be empty"]
            [:tax present? "Tax can't be empty"]
            [:discount present? "Discount can't be empty"]

            ;; validate type

            [:quantity integer-string? "Quantity has to be an integer number"]
            [:price decimal-string? "Price has to be a number"]
            [:tax decimal-string? "Tax has to be a number"]
            [:discount decimal-string? "Discount has to be a number"]

            ;; validate range

            [:quantity (gt 0) "Quantity can't be negative"]

            ;; other specific platform validations (not at the moment)

            ))  

我写了一些宏,但我的函数没有编译。我不明白为什么

(ns try-cljs.shopping.validators
  (:require [valip.core :refer [validate]]
            [valip.predicates :refer [present?
                                      integer-string?
                                      decimal-string?
                                      gt]]))

(defmacro empty-checks []
  `(list ~@(map (fn [x] [(keyword x) 'present? (str x " can't be empty")])
                ["quantity" "price" "tax" "discount"])))

(defmacro number-checks []
  `(list ~@(mapv (fn [x] [(keyword x) 'decimal-string? (str x " should be a number")])
                 ["price" "tax" "discount"])))

(defmacro quantity-checks []
 `(list [:quantity integer-string? "quantity should be an integer"]
        [:quantity (gt 0) "quantity should be positive"]))

(defmacro checks [] `(list ~@(empty-checks)
                           ~@(number-checks)
                           ~@(quantity-checks)))

(defn validate-shopping-form [quantity price tax discount]
  (apply validate {:quantity quantity :price price :tax tax :discount discount}
                  (checks)))

错误是:java.lang.IllegalArgumentException: No matching ctor found for class valip.predicates$gt$fn__2332

它是关于(gt 0)形式的。但是这意味着什么以及如何修复宏?

顺便说一下。在REPL表达式中

(apply validate {:quantity "10"} (concat (quantity-checks)))

工作正常

1 个答案:

答案 0 :(得分:1)

首先要注意的是,你真的不需要任何宏。我假设您想使用宏来了解宏。

为了解释这个问题,我将使用检查功能的简化版本:

(defmacro checks [] `(list ~@(quantity-checks)))

当遇到宏问题时,您需要比较要生成的代码与宏实际生成的代码的对比。

您想要生成的内容:

(clojure.core/list
 [:quantity valip.predicates/integer-string? "quantity should be an integer"]
 [:quantity (valip.predicates/gt 0) "quantity should be positive"])

要查看您实际生成的内容,请使用(clojure.pprint/pprint (macroexpand '(checks)))

(clojure.core/list
 [:quantity #object[valip.predicates$integer_string_QMARK_ 0x13272c8f "valip.predicates$integer_string_QMARK_@13272c8f"] "quantity should be an integer"]
 [:quantity #object[valip.predicates$gt$fn__25100 0x17fe6151 "valip.predicates$gt$fn__25100@17fe6151"] "quantity should be positive"])

#object[valip.predicates$gt$fn__25100... gibberish是(gt 0)返回的fn的toString表示。

因此,您的宏正在执行(gt 0),而不是返回代表(gt 0)调用的代码。这是因为quantity-check是一个宏,因此在编译checks宏之前会对其进行评估。有关详细说明,请参阅this question

为避免宏执行该函数,您只需引用表达式:

(defmacro quantity-checks []
  `(list [:quantity 'integer-string? "quantity should be an integer"]
         [:quantity '(gt 0) "quantity should be positive"]))

如果您使用此版本运行macroexpand,您将看到生成与我们想要的代码相同的代码。

请注意,数量检查可以是以下函数:

(defn quantity-checks []
 `([:quantity integer-string? "quantity should be an integer"]
   [:quantity (gt 0) "quantity should be positive"]))

后引用不包括宏。