具有命名参数

时间:2017-04-06 13:29:16

标签: clojure clojure.spec

假设我们有一个函数clothe,除了许多可选的命名参数person:hat:shirt之外,还需要一个位置参数:pants

(defn clothe [person & {:keys [hat shirt pants]}]
  (str "Clothing " person " with " hat shirt pants "."))
(clothe 'me :hat "top hat")
=> "Clothing me with top hat."

我目前为此函数编写规范的方法是:

(require '[clojure.spec     :as spec]
         '[clojure.spec.gen :as gen])

(spec/def ::person symbol?)
(spec/def ::clothing
  (spec/alt :hat   (spec/cat :key #{:hat}   :value string?)
            :shirt (spec/cat :key #{:shirt} :value string?)
            :pants (spec/cat :key #{:pants} :value string?)))

(spec/fdef clothe
           :args (spec/cat :person  ::person
                           :clothes (spec/* ::clothing))
           :ret string?)

问题在于它允许参数列表,如

(clothe 'me :hat "top hat" :hat "nice hat")
=> "Clothing me with nice hat."

即使语言本身允许,也可能是一个错误。但也许比这更糟糕的是它使得生成的数据对于通常调用函数的方式不切实际:

(gen/generate (spec/gen (spec/cat :person  ::person
                                  :clothes (spec/* ::clothing))))
=> (_+_6+h/!-6Gg9!43*e :hat "m6vQmoR72CXc6R3GP2hcdB5a0"
    :hat "05G5884aBLc80s4AF5X9V84u4RW" :pants "3Q" :pants "a0v329r25f3k5oJ4UZJJQa5"
    :hat "C5h2HW34LG732ifPQDieH" :pants "4aeBas8uWx1eQWYpLRezBIR" :hat "C229mzw"
    :shirt "Hgw3EgUZKF7c7ya6q2fqW249GsB" :pants "byG23H2XyMTx0P7v5Ve9qBs"
    :shirt "5wPMjn1F2X84lU7X3CtfalPknQ5" :pants "0M5TBgHQ4lR489J55atm11F3"
    :shirt "FKn5vMjoIayO" :shirt "2N9xKcIbh66" :hat "K8xSFeydF" :hat "sQY4iUPF0Ef58198270DOf"
    :hat "gHGEqi58A4pH2s74t0" :pants "" :hat "D6RKWJJoFLCAaHId8AF4" :pants "exab2w5o88b"
    :hat "S7Ti2Cb1f7se7o86I1uE" :shirt "9g3K6q1" :hat "slKjK67608Y9w1sqV1Kxm"
    :hat "cFbVMaq8bfP22P8cD678s" :hat "f57" :hat "2W83oa0WVWM10y1U49265k2bJx"
    :hat "O6" :shirt "7BUJ824efBb81RL99zBrvH2HjziIT")

更糟糕的是,如果您碰巧使用spec/*进行递归防御,则无法限制在对代码运行测试时生成的潜在递归事件的数量。

那么我的问题就变成了:有没有办法为一个函数指定命名参数,将每个键的出现次数限制为一个?

1 个答案:

答案 0 :(得分:2)

如果我们看一下requireclojure.core.specs宏的方式,我们可以看到它使用(spec/keys* :opt-un [])来指定依赖项列表中的命名参数,例如{{1 {}}中的{}和:refer

:as

文档未提及(ns (:require [a.b :as b :refer :all]))(s/def ::or (s/map-of simple-symbol? any?)) (s/def ::as ::local-name) (s/def ::prefix-list (s/spec (s/cat :prefix simple-symbol? :suffix (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list)) :refer (s/keys* :opt-un [::as ::refer])))) (s/def ::ns-require (s/spec (s/cat :clause #{:require} :libs (s/* (s/alt :lib simple-symbol? :prefix-list ::prefix-list :flag #{:reload :reload-all :verbose}))))) 的用途,但另一方面,Spec Guide提到它们用于指定非限定密钥。回到我们的函数辩护,我们可以把它写成:

:req-un

遗憾的是,这对接受同一命名参数

的多个实例的函数没有帮助
:opt-un

虽然它确实意味着生成器最大限度地生成相同键的一个实例,这有助于递归规范。

(spec/def ::clothing (spec/keys* :opt-un [::hat ::shirt ::pants]))
(spec/def ::hat   string?)
(spec/def ::shirt string?)
(spec/def ::pants string?)
(spec/fdef clothe
           :args (spec/cat :person  ::person
                           :clothes ::clothing)
           :ret string?)