具有命名参数的函数的实际Clojure规范

具有命名参数的函数的实际Clojure规范,clojure,clojure.spec,Clojure,Clojure.spec,假设我们有一个函数Cloth,除了一些可选的命名参数:hat,:shirt和:pants,还需要一个位置参数person (脱衣服[人&{:钥匙[帽子衬衫裤子]}] (str“衣服”带“帽子衬衫裤子”。) (给我穿上:帽子“顶帽子”) =>“给我戴上礼帽。” 我目前编写此函数规范的方法是: (需要“[clojure.spec:as spec] “[clojure.spec.gen:as gen]) (规格/定义::人员符号?) (规格/定义::服装 (spec/alt:hat(spec/cat

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

(脱衣服[人&{:钥匙[帽子衬衫裤子]}]
(str“衣服”带“帽子衬衫裤子”。)
(给我穿上:帽子“顶帽子”)
=>“给我戴上礼帽。”
我目前编写此函数规范的方法是:

(需要“[clojure.spec:as spec]
“[clojure.spec.gen:as gen])
(规格/定义::人员符号?)
(规格/定义::服装
(spec/alt:hat(spec/cat:key{:hat}:value字符串?)
:shirt(spec/cat:key#{:shirt}:value字符串?)
:pants(spec/cat:key#{:pants}:value字符串?)
(规格/fdef服装)
:args(规格/类别:个人::个人
:衣服(规格/*::衣服)
:ret字符串?)
问题是它允许参数列表,比如

(给我穿上:帽子“顶帽子”:帽子“漂亮的帽子”)
=>“给我戴上漂亮的帽子。”
尽管语言本身允许这样做,但无论何时犯下这样的错误都可能是错误的。但可能更糟糕的是,它使生成的数据与通常调用函数的方式不符:

(gen/gen)(spec/gen)(spec/cat:person::person
:衣服(规格/*::衣服)
=>(u+6+h/!-6Gg9!43*e:hat“m6vQmoR72CXc6R3GP2hcdB5a0”
:帽子“05G5884aBLc80s4AF5X9V84u4RW”:裤子“3Q”:裤子“A0V329R25F3K5OJ4UZJQA5”
:帽子“C5H2HW34LG732FPQDIEH”:裤子“4aeBas8uWx1eQWYpLRezBIR”:帽子“C229mzw”
:衬衫“Hgw3EgUZKF7c7ya6q2fqW249GsB”:裤子“byG23H2XyMTx0P7v5Ve9qBs”
:衬衫“5wPMjn1F2X84lU7X3CtfalPknQ5”:裤子“0M5TBgHQ4lR489J55atm11F3”
:衬衫“FKn5vMjoIayO”:衬衫“2N9xKcIbh66”:帽子“K8xSFeydF”:帽子“sQY4iUPF0Ef58198270DOf”
:hat“gHGEqi58A4pH2s74t0”:裤子“”:hat“D6RKWJJoFLCAaHId8AF4”:裤子“exab2w5o88b”
:帽子“S7Ti2Cb1f7se7o86I1uE”:衬衫“9g3K6q1”:帽子“slKjK67608Y9w1sqV1Kxm”
:hat“CffVMAQ8BFP22P8CD678S”:hat“f57”:hat“2W83oa0WVWM10y1U49265k2bJx”
:帽子“O6”:衬衫“7BUJ824efBb81RL99zBrvH2HjziIT”)
更糟糕的是,如果您碰巧使用
spec/*
进行递归防御,则无法限制在代码上运行测试时生成的潜在递归发生次数


因此,我的问题是:有没有一种方法可以为函数指定命名参数,将每个键的出现次数限制为一个?

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

文档中没有提到
: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?)
遗憾的是,这对函数接受同一命名参数的多个实例没有帮助

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