Question: how to spec optional keyword arguments ?

556 views
Skip to first unread message

Khalid Jebbari

unread,
Jan 16, 2018, 6:26:13 AM1/16/18
to Clojure
Hello,

Even after reading carefully the official spec guide and testing in the REPL, I'm not sure I'm `spec`'ing correctly a function that accepts optional keyword args.

Here's how I do it in a dumb example :

```
(require '[clojure.spec.alpha :as s])

(defn adder [a b & {:keys [c] :or {c 1}}]
   (+ a b c))

(s/def ::a integer?)
(s/def ::b integer?)
(s/def ::c integer?)

(s/fdef adder
          :args (s/cat :a integer? :b integer? (s/? (s/keys* :opt-un [::c])))
          :ret string?)
```

Is this correct ?

Alex Miller

unread,
Jan 16, 2018, 7:36:47 AM1/16/18
to Clojure
You don’t need the final s/? but otherwise that’s right!

Khalid Jebbari

unread,
Jan 16, 2018, 9:07:19 AM1/16/18
to Clojure
Thank you Alex.

It happens that I forgot to name the kwargs part in the s/cat call, so the working version should be :

(s/fdef adder
          :args (s/cat :a integer? :b integer? :kwargs (s/keys* :opt-un [::c]))
          :ret string?)

I'm asking because I've just discovered something about s/cat + s/or + s/keys* (after 1 day and a half of trials).

Before I added the keyword args spec, the specs of my function were like this (simplified) :

(s/fdef adder
        :args (s/or :version1 (s/cat :a integer? :b integer?)
                    :version2 (s/cat :a number? :b number?)
                    :ret integer?))

This version is working just fine, with stest/check etc.

When I added keyword args spec yesterday, I added it like this :

(s/fdef adder
        :args (s/cat :normal-args (s/or :version1 (s/cat :a integer? :b integer?)
                                        :version2 (s/cat :a float? :b float?))
                     :kwargs (s/keys* :opt-un [::c]))
        :ret number?)

It produced some strange behavior. Under `stest/check`, the arguments passed to the function were in a messed up form. The smallest input found by stest/check is `:smallest [((1.0 1.0))]` which looks like a sequence inside a sequence. When I finally realized that I couldn't "un-factorize" the spec of the keyword args and the put it into each s/or branch, it worked :

(s/fdef adder
        :args (s/or :version1 (s/cat :a integer? :b integer? :kwargs (s/keys* :opt-un [::c]))
                    :version2 (s/cat :a float? :b float? :kwargs (s/keys* :opt-un [::c])))
        :ret number?)

I realize that nesting s/cat calls can produce nested sequences, but in my case it seemed natural at first to factorize the spec of the keyword args and express the specificity of the "normal args" separately. It's like check couldn't somehow "unroll" the nested s/cat...

My question is: does it make sense ? Should it work and it's a bug ? Is there no way to factorize like I tried (I would be fine with this really).

I hope I made my question clear, it took me quite some time to nail it and create a reproduction case.

Alex Miller

unread,
Jan 16, 2018, 9:10:04 AM1/16/18
to Clojure
There is a known bug with conforming trailing ? parts inside a regex spec - sounds like you might be seeing that. 

Reply all
Reply to author
Forward
0 new messages