Fork me on GitHub

I have trouble running stest/check in a repl. It just returns an empty vector immediately, just as if the symbol was undefined. I have required the symbol and I can evaluate it in the REPL but it seems the check can’t find the symbol. How can I make sure that stest/check finds the function that I gave to it?


I'm very excited about the goal of clojure.spec leading to improved error messages, but there is still a ways to go. I just typo'd :require in

(ns raven.intro
  (:requre [clj-http.client :as http]
           [raven.secrets :as secrets]))
and got the following user-friendly error:
CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/ns did not conform to spec:
In: [1] val: ((:requre [clj-http.client :as http] [raven.secrets :as secrets])) fails at: [:args] predicate: (cat :docstring (? string?) :attr-map (? map?) :clauses :clojure.core.specs.alpha/ns-clauses),  Extra input
:clojure.spec.alpha/spec  #object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x1331d7d5 "clojure.spec.alpha$regex_spec_impl$reify__1200@1331d7d5"]
:clojure.spec.alpha/value  (raven.intro (:requre [clj-http.client :as http] [raven.secrets :as secrets]))
:clojure.spec.alpha/args  (raven.intro (:requre [clj-http.client :as http] [raven.secrets :as secrets]))
 #:clojure.spec.alpha{:problems [{:path [:args], :reason "Extra input", :pred (clojure.spec.alpha/cat :docstring (clojure.spec.alpha/? clojure.core/string?) :attr-map (clojure.spec.alpha/? clojure.core/map?) :clauses :clojure.core.specs.alpha/ns-clauses), :val ((:requre [clj-http.client :as http] [raven.secrets :as secrets])), :via [], :in [1]}], :spec #object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x1331d7d5 "clojure.spec.alpha$regex_spec_impl$reify__1200@1331d7d5"], :value (raven.intro (:requre [clj-http.client :as http] [raven.secrets :as secrets])), :args (raven.intro (:requre [clj-http.client :as http] [raven.secrets :as secrets]))}, compiling:(/home/deg/Documents/git/projects/raven/src/clj/raven/intro.clj:1:1)


there's room for improvement for sure, there isn't a lib that does human readable translation yet?


It takes a lot less than that. Just a spec that had a list of valid keywords for the ns form could trivially pump out ":requre is not one of [:import :require :use ...]"

Alex Miller (Clojure team)15:06:15

deg: It’s actually a lot trickier than this from a spec perspective due to the “form” structure of the dsl. If we were to design the ns api now, it is highly unlikely we would do it this way (would probably be a map). This turns out (due to the high fanout and nested differences) to be a particularly challenging case for spec to generically give a good error message.


I guess it goes against the choice of making specs maps open to extension tho (which is arguably good or bad)


The problem is that there are very few people in the community who are simultaneously likely to (1) hit this kind of error; (2) take more than a few seconds to spot the problem and feel the pain; and (3) have the comfort level to dive into the source of clojure core.


even tho it's not a map here


sure I agree, personally I wish we could specify strict sets for some kind of specs


not sure how it's implemented for ns tho


And you are right too, of course, that spec's philosophy is generally against closed lists of keywords. But, enough of us have written that for our own purposes. And, I'm sure that very few people would be against tight checking for the sake of error checking of special forms or canonical macros.

Alex Miller (Clojure team)15:06:23

deg: just because it’s hard for spec to produce a good generic error here does not mean that the ns macro can’t take matters into its own hands instead to provide a customized response. not a done deal.


yeah but here it actually is strict, my bad 🙂


it's just the error that s cryptic


And, relatively easy to fix any one error. The challenge is creating a framework where, anytime a newbie is bitten by one of these messages and reports it, it triggers a process that makes that message be forever better for the next user.


if you specify the legal keywords for the NS form as a set, then that's still very much open to extension, no?


just add more keywords to the set if the form ever gains more functionality


@jjttjj one other thing to consider is: what is user? because in my experience that can vary wildly inside of your system, a login user might require login and password while a registering user might require much more, think about this, and you end up having more specific entities (like: login-user and new-user) or you might drop the entities are all (that might vary a lot depending on much re-use you can give to those entities)


I’m a bit confused by the value of “in” when using “map-of”. When there is something wrong a value in the map, the in path doesn’t actually seem to point to that value. For example:


@vikeri Are you sure you have fdef'd the function?

(defn foo [x] x)
=> #'sandbox.spec/foo
(s/fdef foo :args (s/cat :x int?))
=> sandbox.spec/foo
(stest/check `foo)
({:spec ...,
  :clojure.spec.test.check/ret {:result true,
                                :num-tests 1000,
                                :seed 1498486054141},
  :sym sandbox.spec/foo})


@vikeri If you fail to either (1) fdef a defined function, or (2) give check a symbol which does not resolve to a function, it will exhibit the behavior you describe:

(defn bar [x] x)
=> #'sandbox.spec/bar
(stest/check `bar)
=> ()

(stest/check `not-a-function)
=> ()


is there any way to provide a custom message in the output of s/explain?


thanks. Are there any plans for it, or is it out of scope/a bad idea?

Alex Miller (Clojure team)15:06:53

there are no plans for it right now. the idea is that specs should be able to give you generically consistent errors. The explain-data error you get has enough info that you should be able to build custom user errors from that if desired - I believe Sean Corfield is someone doing a lot of this right now.


I get it. I guess I have a slightly more complex case where I use s/& to further validate a conformed value and s/explain reports the whole code of the anonymous function as the failing predicate, which is useful, but it would be even better to have a human readable message to say what was expected


It looks like that: val: ... fails spec: :monitor.settings/aliases-header predicate: (fn [{:keys [aliases]}] (apply distinct? (map :alias aliases)))


you can create custom Spec impl but it's risky


@U050SC7SV I don’t want it that bad, it’s more of a “nice to have” for me 🙂

Alex Miller (Clojure team)16:06:42

s/& is indeed a special case and we are likely going to have to add support for custom forms there regardless (to support things like s/keys* which use it for implementation), but unlikely we would have a custom explain there beyond that


@alexmiller ok, thanks for the explanation and thanks for your efforts in general!


One way to make this a bit more self documenting would be for the second argument of s/& to be named function with a descriptive name instead of an in-place anonymous function


@joshjones Hmm, I required the ns where the fn was defined but maybe that didn’t define the fdef. That is probably the issue.


Thanks for the pointer


A simpler example: of how :in works with map-of. I think I’m beginning to understand how the path works. [:hi 0] means something like “construct a key/value pair from the map and the key :hi, then navigate to the 0th element of that k/v pair”

Alex Miller (Clojure team)15:06:32

correct - maps are conformed as a sequence of map entries (spec’ed as a tuple of k and v)

Alex Miller (Clojure team)15:06:50

however, that path won’t get you to the right place so I think that 0 is actually going to get you to the wrong place.

Alex Miller (Clojure team)15:06:10

and that seems like a bug

Alex Miller (Clojure team)15:06:44

and would be happy to see a jira about that. it is related to but not addressed by that ticket


I've been looking at it for a few minutes now @alexmiller -- and I was going to say it looked like a bug too (but was not confident enough that my understanding about it was correct). the second element of the :in in this case is which element of the tuple spec caused the error. But the first element (`"hi"`) is coming from the every-impl spec, and this part particularly does not seem correct


yes, it seems so


putting some println's in the spec code:

(clojure.spec.alpha/explain-data :foo/user-map {"hi" "foo"})
EVERY IMPL, mapping
i:  0 
v: [hi foo] 

TUPLE IMPL, mapping
i:  0 
form: clojure.core/string? 
pred: #object[clojure.core$string_QMARK___6415 0x674c583e clojure.core$string_QMARK___6415@674c583e] 

TUPLE IMPL, mapping
i:  1 
form: clojure.core/int? 
pred: #object[clojure.core$int_QMARK_ 0x272031ce clojure.core$int_QMARK_@272031ce] 

=> #:clojure.spec.alpha{:problems ({:path [1], :pred clojure.core/int?, :val "foo", :via [:foo/user-map], :in ["hi" 1]}), :spec :foo/user-map, :value {"hi" "foo"}}

Alex Miller (Clojure team)16:06:04

I would consider this on top of the patch for 2080, which improves things in the explain case, whereas this is the conform case which doesn't currently use the kfn

Alex Miller (Clojure team)16:06:09

Rich and I have an ongoing discussion/argument about what happens here :)


i can see why, as it's not necessarily a clear cut answer as to what should be there. from one of your spec slides, I have that :in represents: "vector of specs from root spec to failing spec"


@bbrinck it seems your confusion was justified 😉


@alexmiller @joshjones I appreciate the info. I will look at the tickets and patches mentioned above and file a follow up bug. I agree it’s tricky - especially in the case where the key is wrong. Given a nested data structure, how do I provide a path to a map key? AIUI, it doesn’t work if I consider a “path” to equal “a vector of keys” that would work with get-in.


What I’ve started to do is try to construct a function (maybe this already exists?) that takes some data + an :in path and retrieves the value.


That’s really what is driving this: I’d like to be able to take some (invalid) data + a problem (which contains the :in) and be able to use the unique :in value to get the problematic value, which is likely deep inside the original data


I had originally (naively) thought that I could use get-in for that function, but I think it’s more subtle than that, so I’m writing my own to use these special :in paths.

Alex Miller (Clojure team)16:06:27

the idea is that you should be able to do that


isn't that impossible if you also want to be able to reach keys?

Alex Miller (Clojure team)16:06:16

it’s not possible in all cases

Alex Miller (Clojure team)16:06:55

but I don’t know that it’s useful to specify [<key> 0] either


Yeah, tricky. For my cases, I’m considering changing my code to accommodate a “spec path” that allows me to reach keys i.e. writing new functions that work like get-in or update-in with this new type of path


just a thought, not a question: I think I’ll write a few “readable” and size constrained generators for strings, keywords and symbols so that my eyes don’t bleed whenever I try to read the output of s/exercise (and I know it’s a good thing that it produces “challenging” output)