Fork me on GitHub

likely a common question, and also likely not something spec is suited for, but i’m curious even so - i have a map with start and end dates. is there a spec pattern for declaring a relationship between those two values, i.e. one must be larger than the other? one structure that occurs is spec/fdef’s :ret, but i’m wondering if perhaps the spec api has something else like this?


@robert-stuttaford you can use s/and + a conformer function


Something like

(s/def ::dates
   (s/keys :req-un [::start ::end])
   (fn [{:keys [start end]}]
     (< start end))))
where < is the comparison function of your choice.


wonderful, thank you! that seems ridiculously simple, in hindsight. like, ‘how did i not see this’ simple


@borkdude i guess i’d have to write my own generator too then


Don’t know if the default generator generates enough samples where the conformer can strip away the invalid ones. Not as efficient as writing your own, but it could work


it’s about 50% chance for each sample


when you write an fdef in another namespace than the function, require the namespace where the function lives? or don’t and just fully qualify the symbol in fdef? hmm


Right now I have an init namespace that just requires them all, so no cyclic dependencies and I can just fully qualify in fdef


what’s causing you to want to keep the fdef separate, @borkdude?


because it’s more than 100 lines


I feel like I remember there being some kind of way to add conformers to specs "locally"? Is that a correct memory?


ah 🙂 Qualifies fn-sym with resolve, or using *ns* if no resolution found. seems to suggest that fully-qualified is fine, and probably better for discoverability


I have an interesting problem:

(s/def ::selection
    (s/keys :req-un [:selection/options
            :opt-un [:selection/default-option
    ;; default option must be one of the option ids
    (fn [dropdown]
      (if-let [opt (:default-option dropdown)]
         (set (map :id (:options dropdown)))

(s/def ::dropdown ::selection)
I want the id in dropdown to be optional… Maybe I should make an extra spec without the required id and then make the id in selection required with s/and?


Like this:

(s/def ::selection
   (s/and ::selection*
          (s/keys :req-un [:selection/id]))))

(s/def ::selection*
    (s/keys :req-un [:selection/options]
            :opt-un [:selection/default-option
    ;; default option must be one of the option ids
    (fn [dropdown]
      (if-let [opt (:default-option dropdown)]
         (set (map :id (:options dropdown)))

(s/def ::dropdown ::selection*)


Seems to work


what’s the blessed method for checking whether a spec is registered? s/spec? seems to be for something else


@robert-stuttaford brute force method: use a println in a conformer 😛


or just make an obvious mistake and if no exception, then no 😉


-grin- its for datomic attrs. the code using the spec can’t assume a spec is registered; it has to check first before it attempts to use it to validate.


grr, i’m having to wrap my defmulti with a normal defn so that i can instrument it. otherwise defmethods defined after instrumentation fail


Funny that s/spec? doesn’t return a boolean


don’t all new fdefs after instrumentation fail?


in the sense that they aren’t instrumented yet

Alex Miller (Clojure team)13:03:31

Yes, although I wouldn’t call that a fail


more appropriate: not yet in effect


argument validation in libraries: going forward, should it be done entirely with s/fdef, meaning no validation happens unless the user instruments the functions?


s/assert is also an option I guess which can be compiled away


but for arguments s/fdef is nicer


the downside is that things are a lot more GIGO for users who don't think to instrument e.g., as a user, can I easily instrument all the spec'd functions in all my libraries without having to know which ones have specs? is that too much? wouldn't it be more efficient to instrument only the functions I'm calling directly?


you can instrument all fdef-ed functions with stest/instrument?


but it has to be after you load their namespaces


maybe adding it to reloaded.repl/reset will be a common thing


but I get what you mean now. so you want to go only one level deep


no transitive fdef checking


that'd be nice, since you probably have a large tree of libraries and don't want to slow down your dev by testing all the interactions between them


What overhead are we talking about? I don’t mind a couple of milliseconds more during dev


totally depends on the libraries and what they're doing


in the extreme case, if specs get added to most of the clojure.core functions, instrumenting those will result in milliyears instead of milliseconds


good question


Here's a question. I have a value that I want to ensure is a keyword, string, or symbol AND that it's string conforms to a particular regexp. Example here:

Alex Miller (Clojure team)20:03:54

that’s not a question :)


I'm working on it ...


I've been down this path before, and what I've found is that the next term in the s/and gets the conformed value from the s/or, a tuple of (say), [:keyword :frob].


That's been fine so far EXCEPT as I'm switching to using Expound, the use of a conformer here is a problem:

(s/explain ::schema/enum-value "this-and-that")
             clojure.lang.ExceptionInfo: Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound
clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound {:form "this-and-that", :val [:string "this-and-that"], :in [], :in' []}, compiling:(/Users/hlship/workspaces/github/lacinia/src/com/walmartlabs/lacinia/expound.clj:50:3)

Alex Miller (Clojure team)20:03:50

this issue is actually discussed in the backchat


Recently? Got a link?

Alex Miller (Clojure team)20:03:38

2 days ago in this room - just scroll up till you see the expound stuff


That discussion wasn't helpful, if its the right one. I think they're hitting the same problem and want Expound to print it differently. I want to modify my spec to not trip over this scenario. s/nonconforming may work!


So my question is, how can I achieve the kind of spec I want in a way that avoids the use of a conformer in the middle.

Alex Miller (Clojure team)20:03:55

another option is to wrap s/nonconforming around s/or

Alex Miller (Clojure team)20:03:06

then you get just the value without the tag

Alex Miller (Clojure team)20:03:12

when you conform that is

Alex Miller (Clojure team)20:03:40

currently that’s an undocumented function but I think it’s likely we will either keep it or add a nonconforming variant of s/or


And in the backchat, one suggestion was to look at pinpointer instead of Expound.


@hlship I will likely be adding a “fallback” feature to expound soonish where you will see the vanilla spec error in this case


Note that will help if you only occasionally use conformers, but not if you have lots of them. Definitely check out pinpointer 🙂


Again, I'm quite willing to modify my spec to bypass this problem.


Ah, sorry, I missed that in the thread. Yes, that’d be the best approach! 🙂


I’ve seen people run into other issues with conformers so it’s probably best to avoid e.g.!searchin/clojure/conformer%7Csort:date/clojure/Tdb3ksDeVnU/uU0NT4x6AwAJ


FWIW, I tried to support conformers in expound but it’s really tricky if you’re just looking at the explain-data


s/nonconforming looks to be just what I want:

(s/explain ::schema/enum-value "this-and-that")
-- Spec failed --------------------


must be a valid GraphQL identifier: contain only letters, numbers, and underscores
BTW why are explicit messages not indented by Expound? Should I file an issue?


Can you modify that to show what you’d prefer?


(I’m always happy to get bug reports too 🙂 if that’s easier to discuss the options)


I’ll say this - off the top of my head, I think it’s working as I intended, but I’m always interested in improving the layout of error messages if it’s not clear


Here's a better example:

(s/explain ::schema/resolve {})
-- Spec failed --------------------


should satisfy



implement the com.walmartlabs.lacina.resolve/FieldResolver protocol
The final line should be indented the same as the fn? line, don't you think?
;; is passed and should return.
(s/def ::resolve (s/or :function ::resolver-fn
                       :protocol ::resolver-type))
(s/def ::resolver-fn fn?)
(s/def ::resolver-type #(satisfies? resolve/FieldResolver %))


Yeah, when it’s part of the “or” it looks weird. I guess I was thinking that since “should” starts on left, custom messages would be the same.


The reason it’s on the left is that it should be in the same spot as “should” like so:

(s/def :example/temp #{:hot :cold})  
(expound/expound :example/temp 1)
;;-- Spec failed --------------------
;;  1
;;should be one of: :cold, :hot

(expound/def :example/name string? "should be a string")
(expound/expound :example/name 1)
;;-- Spec failed --------------------
;;  1
;;should be a string


but I agree that when it’s part of a long “satisfy..or” block, it looks bad. I’ll make a bug


Another expound question (time for its own channel?) :

user=> (require [clojure.spec.alpha :as s])
user=> (s/explain keyword? 3)
val: 3 fails predicate: :clojure.spec.alpha/unknown
user=> (require '[expound.alpha :as expound])
user=> (alter-var-root #'s/*explain-out* (constantly expound/printer))
#object[expound.alpha$printer 0x3c64e2a2 "expound.alpha$printer@3c64e2a2"]
user=> (s/explain keyword? 1)
val: 1 fails predicate: :clojure.spec.alpha/unknown
this works fine when I use set!, but not when I use alter-var-root!. Any ideas?


I’ve always used set! myself, so I’m not sure. Can you talk a little bit more about your use case and neither set! nor binding are a good fit here?


Well, at application startup, might want to alter-var-root, so that any later threads will use the Expound printer, not the default one.


Thanks for that. The short answer is I don’t know unfortunately - this seems to affect any printer, not just expound. Try: (alter-var-root #'s/*explain-out* (constantly (fn [ed] "Hello!"))). It may be a result of the way the REPL is set up - does it reproduce if you put the alter-var-root in the main of an application (as opposed to doing it in the REPL context)?


I suspect it's because clojure.spec is AOTed. I hit something similar with redefining stuff in clojure.test and the hack was to do a RT/loadResourceScript:


Ah, gotcha. I certainly understand your use case and I suspect others may run into the same thing. If you figure it out, let me know and I’ll update the docs. I have a note about using alter-var-root in non-REPL context, which IIRC, works fine, but if your scenario is at the REPL, then no dice


binding should work, as that will carry over into most started threads, including core.async threads.

Alex Miller (Clojure team)23:03:55

I think the difference is that the repl binds explain-out so alter-var-root changes the root but the repl binding is the one being seen

Alex Miller (Clojure team)23:03:30

So you have to set! at the repl