2017-04-06 27 views
1

说,我们有一个功能clothe这就需要另外一个位置参数person了多项可选的命名参数:hat:shirt:pants现实Clojure的规格为函数命名参数

(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") 

而且最糟糕的是,如果你碰巧有一个递归defenition与spec/*没有限制的方式在代码上运行测试时生成的潜在递归事件的数量。

那么我的问题就变成了:是否有一种方法可以将命名参数指定为限制每个键的出现次数的函数?

回答

2

如果我们看一下require宏在clojure.core.specs specced,我们可以看到它使用(spec/keys* :opt-un [])在依赖关系列表来指定命名参数的方式,在(ns (:require [a.b :as b :refer :all])):refer:as

(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}))))) 

文档没有提什么:req-un:opt-un是,但在另一方面,规格指南中提到,他们是指定不合格项。回到我们的函数defenition我们可以把它写成:

(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?) 

可悲的是,这并不与函数接受相同的命名参数

(stest/instrument `clothe) 
(clothe 'me :hat "top hat" :hat "nice hat") 
=> "Clothing me with nice hat." 

的多个实例,帮助尽管它确实意味着,发电机最大产生一个对递归规格有帮助的同一个键的实例。

(gen/generate (spec/gen (spec/cat :person ::person 
            :clothes ::clothing))) 
=> (u_K_P6!!?4Ok!_I.-.d!2_.T-0.!+H+/At.7R8z*6?QB+921A 
    :shirt "B4W86P637c6KAK1rv04O4FRn6S" :pants "3gdkiY" :hat "20o77")