Fork me on GitHub

Hiya folks, does this look like a valid spec? (s/def ::test (s/cat :k keyword? :rest (s/* any?)))


The reason I ask is I get different results with s/valid? and an instrumented function


I.e. (s/valid? ::test [:foo]) => true but when used in s/fdef test-fn :args (s/cat :t ::test) it fails instrumentation


@anticrisis regex-style specs combine unless you wrap them with s/spec as I recall.


So (s/fdef test-fn :args (s/cat :t (s/spec ::test)))


there's something I have a great deal of trouble wrapping my head around with these regexp specs I think


that does the trick, many thanks -- I will meditate on why, for a few hours.


If you do (s/exercise (s/cat :t ::test)) you'll see what's going on...

seancorfield05:07:41 generates sequences and they conform to {:t {:k first-element :rest [other-elements]}}


Perhaps easier to see with this example:

boot.user=> (s/def ::pair (s/cat :a keyword? :b pos-int?))
boot.user=> (s/def ::four (s/cat :ab ::pair :cd ::pair))
boot.user=> (s/valid? ::four [:x 1 :y 2])
boot.user=> (s/conform ::four [:x 1 :y 2])
{:ab {:a :x, :b 1}, :cd {:a :y, :b 2}}


that part makes sense, but I'm not connecting it to the use of (s/spec) to change the behavior of the s/* operator when defining a function spec


I'm scrutinizing the output of s/exercise with and without s/spec right now


To me, the conformed output of (s/exercise (s/cat :t (s/spec ::test))) and (s/exercise (s/cat :t ::test)) look the same


Thanks for tip about s/specwith regexp ops and s/exercise, they point me in the right direction, even if I don't completely understand it. Many thanks.


I still think there's something inconsistent going on, either because I'm misusing this simple s/* form, or because something's broken. Here's a short walkthrough to demonstrate:


(s/def ::test (s/cat :k keyword? :rest (s/* pos-int?)))

(s/conform ::test [:foo 1 2 3 4])
;; => {:k :foo, :rest [1 2 3 4]}

(s/conform (s/spec ::test) [:foo 1 2 3 4])
;; => {:k :foo, :rest [1 2 3 4]}

(s/conform (s/cat :t ::test) [:foo 1 2 3 4])
;; => {:t {:k :foo, :rest [1 2 3 4]}}

(s/conform (s/cat :t (s/spec ::test)) [:foo 1 2 3 4])
;; => :clojure.spec.alpha/invalid

(defn test-fn [test] nil)
(s/fdef test-fn :args (s/cat :t ::test) :ret nil?)

(stest/instrument `test-fn)
(test-fn [:foo 1 2 3 4])
;; => exception

(s/fdef test-fn :args (s/cat :t (s/spec ::test)) :ret nil?)

(stest/instrument `test-fn)
(test-fn [:foo 1 2 3 4])
;; => nil

;; Note that in order to get test-fn to pass instrumentation,
;; its :args must use the same form which conform considers invalid


(s/conform (s/cat :t (s/spec ::test)) [[:foo 1 2 3 4]])
One element, of type ::test.


similarly your function accepts one argument, of type ::test


The :args in s/fdef is the sequence of arguments.


For your first s/fdef -- :args (s/cat :t ::test) -- your test-fn would have to be (defn test-fn [kw & nums] nil) -- first argument :k, a keyword, and the :rest of the arguments are numbers.


I see what you're saying... but I thought :args (s/cat :x ::whatever) meant "the arguments are a list of one element, labeled 'x', which can be conformed with spec '::whatever'


Your example of fdef'ing [kw & nums] is helpful to tell me I've been thinking about this all wrong. I would have fdef'd it like this: :args (s/cat :k keyword? :nums (s/* pos-int?)) -- which you're telling me is the same as :args ::test-- which it is, but those particular neurons in my brain apparently weren't connected.


For the record, I'd like to point out that nesting data structures is confusing. Is there a version of clojure I can use that doesn't do that? Thanks. 🙂


If you want actual sequences (and nested sequences) then use coll-of etc, not cat 😸


How can I stub a function? Typically I’d like to require that on-click is a function, but it fails when I later exercise it since theres no generator for it


Creating a custom generator would be overkill I think


@hkjels would a custom generator of (gen/return (constantly nil)) be overkill?


Hmmm… I’ve just made an observation about specs which I’m wondering if others could validate for me… Basically I have a layered architecture where we do something like this: (->> input transform-1 transform-2 transform-3) and I’ve just realised that in layout terms it seems better to organise the input specs alongside the transforms, rather than the :ret specs. It also seems to more closely align with things like instrument checking :args rather than :rets Has anyone else noticed this?


What is the most idiomatic way to spec something to be a single value? I’m guessing: (s/def ::single-value #{:foo})


yep, you get free gen with that one


mpenet: yeah the free gen is a big motivator 🙂


in theory (constantly :foo) would work for validation but no gen


(constantly :foo) would work? That seems identical to any? to me


it's a predicate that always returns truthy


bad example


#(= % :foo) I guess?


yes or #(identical? :foo %)


just for keywords


but anyway, the set solutions is better in most case


I can imagine people stumbling on #{nil} relatively often


well I guess you want nil? in that case


I guess that sort of error isn't likely to go unnoticed


Bumping that question -- I was looking for some prior art for custom printing mostly


@ghadi I’m working on something now, but it’s still pre-alpha


hope to have something ready by thursday 🙂


The code is under change right now, but a preview of what I’m trying to achieve is in the tests e.g.


bbrinck: very cool


Excuse the lack of documentation or sane organization


Has anyone found a use case for specing a map with coll-of? I see that you can create a spec like (s/def :foo/map (s/coll-of int? :kind map? :into {})) but I can’t see why this would be useful since everything but the empty map would always fail.


I suppose you could maybe replace int? in that example with a spec for the key/value pairs, but in that case, map-of seems superior.

Alex Miller (Clojure team)18:07:41

there are some cases where it is useful to spec a map as a sequence of entry tuples

Alex Miller (Clojure team)18:07:17

“hybrid” maps (combination of s/keys and s/map-of styles) is one case


alexmiller: When I look to the result of (s/conform ::binding-form it remember me about ast's ... may in the future, can spec/conform be used as analyzer?

Alex Miller (Clojure team)20:07:30

If you use that in the general sense, then yes absolutely - it’s a great tool for turning s-expressions into a tree of maps describing a DSL syntax


@alexmiller Ah, I see. You’re using a tuple as the “inner” spec. That makes sense. Thanks for the info!

Alex Miller (Clojure team)18:07:27

that’s actually how s/map-of and s/every-kv are implemented


Interesting. Your example was instructive. I can see how oring together several tuples would let me define different valid key/value pairs


Basically, it lets me create a clear relationship between the spec for certain keys and the specs for their values. But combine them all into one big map spec


“valid key/value pairs” Sorry, I meant “valid key/value pairs of specs


Is fspec meant to handle cases where the provided function throws? It’s causing spec errors when instrumented.


I’ve got a relatively minimal repro:

(t/use-fixtures :once #(do (s.test/instrument)

(defn takes-fn [f]
  (try (f true)
       (catch Error e

(s/fdef takes-fn
  :args (s/cat :f (s/fspec :args (s/cat :bool boolean?)
                           :ret boolean?))
  :ret boolean?)

(deftest can-handle-throws
  (is (false? (takes-fn #(if %1
                           (throw (ex-info "scary exception!" {}))


That yields: In: [0] val: (true) fails at: [:args :f] predicate: (apply fn), scary exception! when the test is run

Alex Miller (Clojure team)20:07:04

when you have an fdef with an fspec arg, the instrumented var will check the fspec by generating from its args spec and invoking the function to validate it conforms to the spec

Alex Miller (Clojure team)20:07:27

in this case it will generate boolean values and invoke f

Alex Miller (Clojure team)20:07:41

specs cover non-exceptional use (as those are the exceptions :) so I think a more accurate fspec args spec in this case would use false?, not boolean?. it would then invoke f with only false and not trigger the exception


Right I guess I just wasn't expecting the fspec'd arg to be invoked? Wouldn't it be better to use with-gen so the spec still captures the allowed range of values?

Alex Miller (Clojure team)22:07:20

well the idea is that you said this function takes a function that responds a certain way - the only way to validate that is to invoke it


Yeah agreed. I don’t think that was really a question, I was just kinda surprised. I would have assumed that the fn would be instrumented and validated when it was invoked rather than when it was passed into another function. Might be something worth mentioning in the docs, if it isn’t already?

Alex Miller (Clojure team)22:07:27

This has been brought up several times but I haven't had a chance to discuss it with Rich


Ok. Thanks for the clarification and explanation in any case, it’s much appreciated.