【问题标题】:Plumatic Schema for keyword arguments关键字参数的 Plumatic Sc​​hema
【发布时间】:2017-09-29 17:29:15
【问题描述】:

假设我们有一个函数get-ints,它有一个位置参数、调用者想要的整数数量以及两个命名参数:max:min,例如:

; Ignore that the implementation of the function is incorrect.
(defn get-ints [nr & {:keys [max min] :or {max 10 min 0}}]
  (take nr (repeatedly #(int (+ (* (rand) (- max min -1)) min)))))

(get-ints 5)               ; => (8 4 10 5 5)
(get-ints 5 :max 100)      ; => (78 43 32 66 6)
(get-ints 5 :min 5)        ; => (10 5 9 9 9)
(get-ints 5 :min 5 :max 6) ; => (5 5 6 6 5)

如何为get-ints 的参数列表编写 Plumatic Sc​​hema,一个、三个或五个项目的列表,其中第一个始终是数字,后面的项目始终是关键字和关联值的对.

使用 Clojure Spec 我将其表达为:

(require '[clojure.spec :as spec])
(spec/cat :nr pos-int? :args (spec/keys* :opt-un [::min ::max]))

以及::min::max 持有的有效值的单独定义。

【问题讨论】:

    标签: clojure plumatic-schema


    【解决方案1】:

    根据我从 Plumatic 邮件列表 [0] [1] 得到的答案,我坐下来,在模式语言本身之外编写了我自己的conformer:

    (defn key-val-seq?
      ([kv-seq]
       (and (even? (count kv-seq))
            (every? keyword? (take-nth 2 kv-seq))))
      ([kv-seq validation-map]
       (and (key-val-seq? kv-seq)
            (every? nil? (for [[k v] (partition 2 kv-seq)]
                           (if-let [schema (get validation-map k)]
                             (schema/check schema v)
                             :schema/invalid))))))
    
    (def get-int-args
      (schema/constrained
       [schema/Any]
       #(and (integer? (first %))
             (key-val-seq? (rest %) {:max schema/Int :min schema/Int}))))
    
    (schema/validate get-int-args '())               ; Exception: Value does not match schema...
    (schema/validate get-int-args '(5))              ; => (5)
    (schema/validate get-int-args [5 :max 10])       ; => [5 :max 10]
    (schema/validate get-int-args [5 :max 10 :min 1]); => [5 :max 10 :min 1]
    (schema/validate get-int-args [5 :max 10 :b 1])  ; Exception: Value does not match schema...
    

    【讨论】:

      【解决方案2】:

      我认为在这种情况下,编写您需要的特定代码比尝试使用 Plumatic Sc​​hema 或其他一些不是为此用例设计的工具来强制拟合解决方案更容易。请记住,Plumatic Sc​​hema 和其他工具(如内置的 Clojure 前置和后置条件)只是在违反某些条件时抛出 Exception 的简写方式。如果这些 DSL 都不适合,您始终可以使用通用语言。

      in the Tupelo libraryrel= 函数可以找到与您类似的情况。它旨在执行两个数字之间的“相对相等”测试。它是这样工作的:

      (is      (rel=   123450000   123456789 :digits 4 ))       ; .12345 * 10^9
      (is (not (rel=   123450000   123456789 :digits 6 )))
      (is      (rel= 0.123450000 0.123456789 :digits 4 ))       ; .12345 * 1
      (is (not (rel= 0.123450000 0.123456789 :digits 6 )))
      
      (is      (rel= 1 1.001 :tol 0.01 ))                       ; :tol value is absolute error
      (is (not (rel= 1 1.001 :tol 0.0001 )))
      

      虽然 Tupelo 库中的几乎所有其他函数都大量使用 Plumatic Sc​​hema,但这个是“手动”完成的:

      (defn rel=
        "Returns true if 2 double-precision numbers are relatively equal, else false.  Relative equality
         is specified as either (1) the N most significant digits are equal, or (2) the absolute
         difference is less than a tolerance value.  Input values are coerced to double before comparison.
         Example:
      
           (rel= 123450000 123456789   :digits 4   )  ; true
           (rel= 1         1.001       :tol    0.01)  ; true
         "
        [val1 val2 & {:as opts}]
        {:pre  [(number? val1) (number? val2)]
         :post [(contains? #{true false} %)]}
        (let [{:keys [digits tol]} opts]
          (when-not (or digits tol)
            (throw (IllegalArgumentException.
                     (str "Must specify either :digits or :tol" \newline
                       "opts: " opts))))
          (when tol
            (when-not (number? tol)
              (throw (IllegalArgumentException.
                       (str ":tol must be a number" \newline
                         "opts: " opts))))
            (when-not (pos? tol)
              (throw (IllegalArgumentException.
                       (str ":tol must be positive" \newline
                         "opts: " opts)))))
          (when digits
            (when-not (integer? digits)
              (throw (IllegalArgumentException.
                       (str ":digits must be an integer" \newline
                         "opts: " opts))))
            (when-not (pos? digits)
              (throw (IllegalArgumentException.
                       (str ":digits must positive" \newline
                         "opts: " opts)))))
          ; At this point, there were no invalid args and at least one of 
          ; either :tol and/or :digits was specified.  So, return the answer.
          (let [val1      (double val1)
                val2      (double val2)
                delta-abs (Math/abs (- val1 val2))
                or-result (truthy?
                            (or (zero? delta-abs)
                              (and tol
                                (let [tol-result (< delta-abs tol)]
                                  tol-result))
                              (and digits
                                (let [abs1          (Math/abs val1)
                                      abs2          (Math/abs val2)
                                      max-abs       (Math/max abs1 abs2)
                                      delta-rel-abs (/ delta-abs max-abs)
                                      rel-tol       (Math/pow 10 (- digits))
                                      dig-result    (< delta-rel-abs rel-tol)]
                                  dig-result))))
                ]
            or-result)))
      

      【讨论】:

        猜你喜欢
        • 2016-07-16
        • 2016-09-28
        • 2017-09-26
        • 2017-09-25
        • 2017-09-23
        • 2014-08-14
        • 2015-04-16
        • 2016-09-17
        • 1970-01-01
        相关资源
        最近更新 更多