According to Spec Guide
* regular expression operator: checks 0 or more of a predicate/pattern, as this:
(s/def ::seq-of-keywords (s/* keyword?))
;; opts are alternating keywords and booleans
(s/def ::opts (s/* (s/cat :opt keyword? :val boolean?)))
(s/conform ::opts [:silent? false :verbose true])
;;=> [{:opt :silent?, :val false} {:opt :verbose, :val true}]
But as I understand it, s/cat checks a sequence.
So why (s/* (s/cat)) is not for checking a sequence of sequence each of which conforms to (s/cat).
Something like this:
(s/conform ::opts [[:silent? false] [:verbose true]])
Why does it act like s/* flattened s/cat? Or How can I check for something like [[:silent? false] [:verbose true]]?
You do that by using coll-of:
(s/conform (s/coll-of ::opts) [[:silent? false] [:verbose true]])
=> [[{:opt :silent?, :val false}] [{:opt :verbose, :val true}]]
Sometimes it helps to generate sample data:
(gen/generate (s/gen ::opts))
=> (:aqfR6b*C/. false :?.03/Vu7? false :Y17UL0/McsI5h true)
which does not match the pattern of [[:silent? false] [:verbose true]]
But this one should be probably closer to what you are looking for:
(gen/generate (s/gen (s/coll-of (s/tuple keyword? boolean?))))
=> [[:X_o.u?7i/o.dIgTy false] [:L?*/_WY._:z true] [:X26:-j/l2q!u-7I false]]
Why does ... s/* flatten s/cat?
... because a regular expression is a structured specification of a flat sequence. To paraphrase the Spec Guide to Sequences ...
When regex ops are combined, they describe a single sequence.
To spec a nested sequence, wrap it in an explicit call to s/spec.
How can I spec something like [[:silent? false] [:verbose true]]?
To spec a sequence of pairs, each of which is a keyword followed by a boolean:
(s/def ::opts (s/* (s/spec (s/cat :opt keyword? :val boolean?))))
Note the s/spec wrapping the s/cat. Now, for example, ...
=> (s/conform ::opts [[:silent? false] [:verbose true]])
[{:opt :silent?, :val false} {:opt :verbose, :val true}]
You can, of course, as akond does, use
s/tuple instead of s/catand
s/coll-of instead of s/*
with no need for the interposed s/spec.
Related
How am I supposed to create a spec where all keys are optional but at least one of the specified keys should be present?
(s/def ::my-spec (s/and (help-plz??)(s/keys :opt-un [::a ::b])))
(s/valid? ::my-spec {} => false
(s/valid? ::my-spec {:a 1}) => true
(s/valid? ::my-spec {:b 1}) => true
(s/valid? ::my-spec {:a 1 :b 1}) => true
(s/valid? ::my-spec {:A1 :B 1}) => true
With the current spec alpha, in order to use the same key collection for both the keys spec and the at-least-one-exists check, you'll need to use a macro. (The upcoming spec 2 alpha addresses this by exposing more data-driven APIs for creating specs.)
Here's a quick sketch for your particular example:
(defmacro one-or-more-keys [ks]
(let [keyset (set (map (comp keyword name) ks))]
`(s/and (s/keys :opt-un ~ks)
#(some ~keyset (keys %)))))
(s/def ::my-spec (one-or-more-keys [::foo ::bar]))
(s/conform ::my-spec {:bar nil})
=> {:bar nil}
(s/conform ::my-spec {:baz nil})
=> :clojure.spec.alpha/invalid
Alternatively, you could just define the key collection twice, and use a similar predicate with s/and.
Per the docs for keys:
The :req key vector supports 'and' and 'or' for key groups:
(s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z])
Your code should be:
(s/def ::my-spec (s/keys :req-un [(or ::a ::b)]))
I found a way to throw NullPointerExceptions by generating values using an s/or spec. I want to be able to generate and label a nil value, no s/nilable isn't suitable.
Is this a bug in clojure.spec?
(ns blah
(:require [clojure.spec.alpha :as s]))
(s/def ::fine (s/or :int int? :nil nil?)
(s/def ::throws (s/or :int (s/int-in 1 5) :zero zero? :nil nil?)
(s/exercise ::fine)
=>
([nil [:nil nil]
[-1 [:int -1]]
[nil [:nil nil]]
[0 [:int 0]]
[nil [:nil nil]]
[15 [:int 15]]
[-25 [:int -25]]
[nil [:nil nil]]
[-2 [:int -2]]
[-30 [:int -30]]])]])
(s/exercise ::throws)
=>
NullPointerException clojure.lang.Numbers.ops (Numbers.java:1018)
If you limit the number of times ::throws is exercised, you see correct :int and :zero values, it's the :nil which throws.
This works:
(s/def ::works (s/or :int (s/int-in 1 5) :nil nil? :zero zero?))
I assume spec will try to conform in order of the or alternatives. Conforming nil using the zero? predicate will crash, but moving nil? in front of that, will prevent that from happening.
What also works is using a set instead of a predicate:
(s/def ::throws (s/or :int (s/int-in 1 5) :zero #{0} :nil nil?))
The following clojure spec ::my permits maps having either the key :width or the key :height, however it does not permit having both of them:
(s/def ::width int?)
(s/def ::height int?)
(defn one-of-both? [a b]
(or (and a (not b))
(and b (not a))))
(s/def ::my (s/and (s/keys :opt-un [::width ::height])
#(one-of-both? (% :width) (% :height))))
Even if it does the job:
(s/valid? ::my {})
false
(s/valid? ::my {:width 5})
true
(s/valid? ::my {:height 2})
true
(s/valid? ::my {:width 5 :height 2})
false
the code does not appear that concise to me. First the keys are defined as optional and then as required. Does anyone have a more readable solution to this?
clojure.spec is designed to encourage specs capable of growth. Thus its s/keys does not support forbidding keys. It even matches maps having keys that are neither in :req or opt.
There is however a way to say the map must at least have :width or :height, i.e. not XOR, just OR.
(s/def ::my (s/keys :req-un [(or ::width ::height)]))
This feature is built into spec - you can specify and/or patterns in req-un:
(s/def ::my (s/keys :req-un [(or ::width ::height)]))
:user/my
user=> (s/valid? ::my {})
false
user=> (s/valid? ::my {:width 5})
true
user=> (s/valid? ::my {:height 2})
true
user=> (s/valid? ::my {:width 5 :height 2})
true
Just wanted to pitch in with a small modification to the spec in the original question which logic will fail if the value held by any key is falsey, i.e. false or nil.
(spec/valid? ::my {:width nil})
=> false
Of course this won't occur with the given constraints of int? put on the values in the question. But perhaps someone in posterity allows their values to be nilable or boolean in which case this answer becomes handy.
If we instead define the spec as:
(defn xor? [coll a-key b-key]
(let [a (contains? coll a-key)
b (contains? coll b-key)]
(or (and a (not b))
(and b (not a)))))
(spec/def ::my (spec/and (spec/keys :opt-un [::width ::height])
#(xor? % :width :height)))
we get the result that
(spec/valid? ::my {:width nil})
=> true
(spec/valid? ::my {:width nil :height 5})
=> false
I'm following clojure.spec's guide (http://clojure.org/guides/spec). I'm confused by the difference between alt and or for sequence spec.
For me the two following examples work equally well. So what's the difference between the two?
; Use `alt`
(s/def ::config (s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
; Use `or`
(s/def ::config (s/* (s/cat :prop string?
:val (s/or :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
s/alt is for concatenating nested regex specs where using s/or specifies a nested sequence. In your example it doesn't make a difference since you are not using nested regex specs. Here is an example:
(s/def ::number-regex (s/* number?))
(s/def ::or-example (s/cat :nums (s/or :numbers ::number-regex)))
(s/valid? ::or-example [1 2 3])
;;-> false
(s/valid? ::or-example [[1 2 3]])
;;-> true
As you can see, or specifies a nested sequence in which a new regex context is started, whereas alt specifies the opposite:
(s/def ::alt-example (s/cat :nums (s/alt :numbers ::number-regex)))
(s/valid? ::alt-example [1 2 3])
;;-> true
(s/valid? ::alt-example [[1 2 3]])
;;-> false
From http://clojure.org/guides/spec, we know
When regex ops are combined, they describe a single sequence.
that means if you want to valid the nested sequences, you should do like this.
(s/def ::config (s/*
(s/cat :prop string?
:val (s/spec
(s/alt :s string? :b #(instance? Boolean %))))))
And then your data looks like this (Notice the brackets around)
(s/explain ::config ["-server" ["foo"] "-verbose" [true] "-user" [13]])
Also, if you do (s/or).
(s/def ::config (s/* (s/cat :prop string?
:val (s/spec
(s/or :s string? :b #(instance? Boolean %))))))
your data should be the same as the old one (Notice there are no brackets around)
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
BTW, for non-nested sequences. there is still a little bit difference between (s/alt ) and (s/or):
;;; for (s/or)
(s/def ::name-or-id (s/or :name string?
:id int?))
(s/conform ::name-or-id 42) ;;=> [:id 42]
;;; for (s/alt)
(s/def ::name-or-id (s/alt :name string?
:id int?))
(s/conform ::name-or-id [42]) ;;=> [:id 42]
I'm following clojure.spec's guide (http://clojure.org/guides/spec). I'm confused by the difference between alt and or for sequence spec.
For me the two following examples work equally well. So what's the difference between the two?
; Use `alt`
(s/def ::config (s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
; Use `or`
(s/def ::config (s/* (s/cat :prop string?
:val (s/or :s string? :b boolean?))))
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
s/alt is for concatenating nested regex specs where using s/or specifies a nested sequence. In your example it doesn't make a difference since you are not using nested regex specs. Here is an example:
(s/def ::number-regex (s/* number?))
(s/def ::or-example (s/cat :nums (s/or :numbers ::number-regex)))
(s/valid? ::or-example [1 2 3])
;;-> false
(s/valid? ::or-example [[1 2 3]])
;;-> true
As you can see, or specifies a nested sequence in which a new regex context is started, whereas alt specifies the opposite:
(s/def ::alt-example (s/cat :nums (s/alt :numbers ::number-regex)))
(s/valid? ::alt-example [1 2 3])
;;-> true
(s/valid? ::alt-example [[1 2 3]])
;;-> false
From http://clojure.org/guides/spec, we know
When regex ops are combined, they describe a single sequence.
that means if you want to valid the nested sequences, you should do like this.
(s/def ::config (s/*
(s/cat :prop string?
:val (s/spec
(s/alt :s string? :b #(instance? Boolean %))))))
And then your data looks like this (Notice the brackets around)
(s/explain ::config ["-server" ["foo"] "-verbose" [true] "-user" [13]])
Also, if you do (s/or).
(s/def ::config (s/* (s/cat :prop string?
:val (s/spec
(s/or :s string? :b #(instance? Boolean %))))))
your data should be the same as the old one (Notice there are no brackets around)
(s/explain ::config ["-server" "foo" "-verbose" true "-user" 13])
BTW, for non-nested sequences. there is still a little bit difference between (s/alt ) and (s/or):
;;; for (s/or)
(s/def ::name-or-id (s/or :name string?
:id int?))
(s/conform ::name-or-id 42) ;;=> [:id 42]
;;; for (s/alt)
(s/def ::name-or-id (s/alt :name string?
:id int?))
(s/conform ::name-or-id [42]) ;;=> [:id 42]