如何在宏中正确使用 valip predicate gt?

How to correctly use valip predicate gt with macros?

我正在学习现代 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)))

工作正常

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

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

(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... 乱码是 (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"]))

如果您 运行 使用此版本进行宏扩展,您会看到生成的代码与我们想要的代码相同。

请注意 quantity-checks 可以是这样的函数:

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

Back-quoting不排除宏。