Fork me on GitHub
#clojure-spec
<
2018-06-07
>
Alex Miller (Clojure team)00:06:10

Generally anything like this should be considered a bug (if it’s reproducible). One thing that could possibly lead you down this path at the repl is changing a spec but not redefining a spec that used it. Specs are compiled at definition time so you can get a mixture of things that way.

dpsutton02:06:02

Oh. I made a poor man's version of spec using defs and thought you got around that with the central database of specs and always getting a "fresh" copy. I'll make sure to have up to date definitions. Thanks

Vincent Cantin02:06:39

What is the most idiomatic way to apply s/cat, knowing that it is a macro?

Alex Miller (Clojure team)12:06:55

A future way to do this will be to write a spec for s/cat, write conform data, and s/unform back to a spec.

👍 4
Alex Miller (Clojure team)12:06:16

See CLJ-2112 for some early work on that

noisesmith02:06:26

to apply a macro, you need another macro (and to do it at runtime to something that is not a literal in your source file or created when loading the file you need to be compiling new code, eg. eval)

Vincent Cantin03:06:25

I will use a macro then. Do you know why it was made as a macro? (most importantly, why only as a macro)

Vincent Cantin03:06:43

That would have been good to have the macro and the functions and let the users choose between them.

johanatan19:06:28

Completely agree. The prevalence of macros in spec is my number one complaint with it.

Vincent Cantin03:06:57

s/cat and sm/cat

Vincent Cantin03:06:25

“macro contagion” … it reminds me of the “const hell” in c++

seancorfield03:06:38

@vincent.cantin spec was designed for humans to write, so macros are fine. There is work in the pipeline to open up programmatic access to spec, which will mean functions.

johanatan19:06:13

Macros are not fine. They prevent higher-order code and partial application (to name a couple of issues). And of course they result in 'macro proliferation' as well.

seancorfield03:06:16

So, for now, you might be going through a lot of (macro) pain for nothing, depending on how quickly you need the thing you're trying to build...

Vincent Cantin03:06:39

The task I gave myself is probably making me a complainer by design: I am trying to do the most I can do with Spec.

Vincent Cantin03:06:14

You are right, it is not related to the normal use case.

seancorfield03:06:14

Including lots of things it wasn't designed for, I'm sure 🙂

👍 4
seancorfield03:06:44

It's interesting... over time, the Clojure/core folks have produced a number of things that in the rush of new excitement around a feature, lots of people almost immediately try to do stuff with each new feature that's outside the design goals 🙂

Vincent Cantin05:06:58

@U04V70XH6 I was doing that: https://github.com/green-coder/TIS-100-Spec My problem with the cat being a macro was for the implementation of my token spec.

Vincent Cantin05:06:41

I wanted to see if it was possible to do it that way. Well … it is possible, but not convenient.

seancorfield05:06:38

And how is performance? 👀

Vincent Cantin05:06:54

eh eh .. I don’t know, it does not matter here.

Vincent Cantin05:06:04

I wished that cat could accept nil instead of a token for specs which we do not need to know about when we use conform.

Vincent Cantin05:06:47

in regex terms, it would mean a “non-capturing” expression.

seancorfield05:06:33

I'm wondering how many times folks have to say "spec is not a parser"... ? :rolling_on_the_floor_laughing:

Vincent Cantin05:06:49

😄 that means that the community would like to use it in that way too.

seancorfield05:06:24

If you get the grammar right and each spec generates then you have the possibility of generating random but syntactically valid TIS-100 programs... that you could then run and see what they do! 🙂

Vincent Cantin05:06:47

You start to see some of my goals

dottedmag12:06:21

However spec is definitely sold as a parser, maybe unintentionally. "Check your inputs and, oh here is the parse tree".

dottedmag12:06:54

It's not a best string parser out there, that's for sure.

seancorfield03:06:26

I think it says a lot about how the features have been such great building blocks over the years, as well as how laser-focused many of the features have been.

seancorfield03:06:14

I don't know if you've done much with clj and deps.edn yet? That seems to be attracting the same "let's see what crazy things we can do with this" experimentation.

johanatan19:06:28

I don't consider wanting to apply or partially apply a macro to be a "crazy experimentation". It's very much just a thing that advanced [meta-]programmers want to do to keep their code concise.

favila16:06:22

I can't find a ticket for this, but I can't believe it wasn't noticed before

favila16:06:06

s/every or s/coll-of will interpret :kind as a predicate only (not spec) during s/valid? s/conform etc

favila16:06:25

but during explain, it does interpret as a spec

favila17:06:24

I think it is because the macros generate a cpred unconditionally interpreting kind as a pred

favila17:06:32

example of how this can go wrong:

favila17:06:10

(s/def ::vector vector?)
=> :user/vector
(s/valid? (s/coll-of any? :kind ::vector) [])
=> false
(s/explain (s/coll-of any? :kind ::vector) [])
Success!
=> nil

favila17:06:21

Here, this is even clearer:

favila17:06:24

(s/explain (s/coll-of any? :kind (s/spec vector?)) [])
Success!
=> nil
(s/valid? (s/coll-of any? :kind (s/spec vector?)) [])
ClassCastException clojure.spec.alpha$spec_impl$reify__1987 cannot be cast to clojure.lang.IFn  user/eval3301/fn--3303 (form-init7886713966146839296.clj:1)

dpsutton17:06:40

in checking against the spec, it will call (::vector []) which returns nil and fails the spec. In explaining, it is aware that [] satisfies the ::vector spec

favila17:06:21

(macroexpand '(s/coll-of any? :kind (s/spec vector?)))
=>
(clojure.spec.alpha/every-impl
 (quote any?)
 any?
 {:clojure.spec.alpha/describe (quote
                                (clojure.spec.alpha/coll-of
                                 clojure.core/any?
                                 :kind
                                 (clojure.spec.alpha/spec clojure.core/vector?))),
  :clojure.spec.alpha/conform-all true,
  :clojure.spec.alpha/cpred (fn*
                             [G__3317]
                             (clojure.core/and ((s/spec vector?) G__3317))),
  :kind (s/spec vector?),
  :clojure.spec.alpha/kind-form (quote
                                 (clojure.spec.alpha/spec clojure.core/vector?))}
 nil)

favila17:06:29

Note that :cpred is generated but nonsense

favila17:06:54

> :kind - a pred/spec that the collection type must satisfy, e.g. vector? (default nil) Note that if :kind is specified and :into is not, this pred must generate in order for every to generate.

favila17:06:09

note "pred/spec"

favila17:06:34

ok, found the ticket, nm

bmaddy17:06:33

I'm trying to write a spec for a function that takes in another function. I have the spec for the function passed in, but am getting "Couldn't satisfy such-that predicate after 100 tries.". Is that because I need to write a generator for functions that are arguments?

noisesmith18:06:10

such-that acts as a filter, it's saying it wasn't finding anything that the filter accepted

bmaddy20:06:48

(sorry, was at lunch) Here's how I have it spec'd:

(s/def ::ruleset-id keyword?)
(s/def ::rule-name keyword?)
(s/fdef ::fetching-fn
        :args ::ruleset-id
        :ret (s/coll-of ::rule-name))
(s/fdef rules-for-rulesets
        :args (s/cat :fetching-fn ::fetching-fn
                     :ruleset-ids (s/coll-of ::ruleset-id))
        :ret (s/map-of (s/and set? (s/coll-of ::ruleset-id))
                       (s/and set? (s/coll-of ::rule-name)))
        ;; Ensure each rule only appears in the values only once so we
        ;; don't run them multiple times
        :fn #(->> % :ret vals (reduce concat) frequencies vals (every? (partial = 1))))

taylor20:06:31

it might be the s/ands where the first predicate is set?

taylor20:06:56

could you try changing that (s/and set? (s/coll-of ::the-spec)) to (s/coll-of ::the-spec :kind set?) @bmaddy

taylor20:06:32

(because s/and makes a generator from whatever the first predicate is, and set? is unlikely to produce values that will satisfy ::ruleset-id)

taylor20:06:02

oh, another potential issue: ::fetching-fn's :args spec should probably be (s/cat :whatever ::ruleset-id) instead of just ::ruleset-id?

bmaddy20:06:44

trying it...

bmaddy20:06:26

Oh geez, I think it's the :args (s/cat ... thing. picard-facepalm

taylor20:06:57

yeah I just realized the s/and issue probably has nothing to do with the such-that issue

bmaddy20:06:59

((gen/generate (s/gen ::fetching-fn)) :foo)
[:K5.SY.if_!_._F6U/_
 :p1Je.*-/*
 :LbrF_/ZP]
That's so cool. 😄

taylor20:06:40

yeah FYI the fn generators (I think) just generate functions that'll return values generated from their :ret spec

bmaddy20:06:07

Yeah, that's what I would expect. It's really cool that spec does that though. 😄

bmaddy20:06:19

Thanks for the help @taylor (and others who looked at it!)

👍 4
hlship21:06:15

I've made slight changes:

(s/def ::type (s/or :base-type ::type-name
                    :wrapped-type ::wrapped-type))
(s/def ::wrapped-type (s/cat :modifier ::wrapped-type-modifier
                             :type ::type))
(s/def ::wrapped-type-modifier #{'list 'non-null})
and
(defmsg ::schema/wrapped-type-modifier "a wrapped type: '(list type) or '(non-null type)")

hlship21:06:45

But I get:

(binding [s/*explain-out* expound/printer]
  (s/explain ::schema/field {:type '(something String)}))
-- Spec failed --------------------

  {:type (something ...)}
          ^^^^^^^^^

should be one of: (quote list), (quote non-null)

-- Relevant specs -------

:com.walmartlabs.lacinia.schema/wrapped-type-modifier:
  #{'non-null 'list}
:com.walmartlabs.lacinia.schema/wrapped-type:
  (clojure.spec.alpha/cat
   :modifier
   :com.walmartlabs.lacinia.schema/wrapped-type-modifier
   :type
   :com.walmartlabs.lacinia.schema/type)
:com.walmartlabs.lacinia.schema/type:
  (clojure.spec.alpha/or
   :base-type
   :com.walmartlabs.lacinia.schema/type-name
   :wrapped-type
   :com.walmartlabs.lacinia.schema/wrapped-type)
:com.walmartlabs.lacinia.schema/field:
  (clojure.spec.alpha/keys
   :req-un
   [:com.walmartlabs.lacinia.schema/type]
   :opt-un
   [:com.walmartlabs.lacinia.schema/description
    :com.walmartlabs.lacinia.schema/resolve
    :com.walmartlabs.lacinia.schema/args
    :com.walmartlabs.lacinia.schema/deprecated])

-------------------------
Detected 1 error
=> nil

hlship21:06:55

I'd expect it to say:

should be a wrapped type: '(list type) or '(non-null type)

bbrinck21:06:51

@hlship Ah, yes, I’ve run into this as well. The problem is that defmsg is narrowly applied to predicates, not any type of spec.

hlship21:06:32

Associates the spec named `k` with `error-message`.
doesn't make that clear. Hm.

bbrinck21:06:42

Agreed, and I don’t think it’s a good design

bbrinck21:06:52

It’s a reflection of the original problem defmsg solved: trying to provide more “human-friendly” messages instead of string? int?

bbrinck22:06:13

but that’s an arbitrary restriction, and I think it should be generalized to any spec, so you can add messages across the board

hlship22:06:50

I'm trying to think of a work-around.

bbrinck22:06:39

I haven’t tried it, but what happens if you replace #{'list 'non-null} with (fn [x] (contains? #{'list 'non-null} x)?

bbrinck22:06:09

(I realize it’s a hack 😞 )

hlship22:06:17

Perhaps you could tweak the logic to treat a set as a predicate

bbrinck22:06:52

Even more generally, I think I should make sure to respect a registered message regardless of the spec type

bbrinck22:06:35

frankly, I wasn’t sure anyone was really using Expound’s capability to register messages, so I wasn’t sure if it was really important. Glad to know you are using it 🙂

bbrinck22:06:48

I’ve recently gone down a rabbit hole of trying to generate specs to test Expound but it’s taken up a lot more time than expected. I’m going to pause that effort and switch over to bug fixing for a bit

hlship22:06:27

I'm very glad you implemented those messages, I think it can make a major difference. In general, I've gotten the rest of the team addicted to Expound ... we use a fair amount of clojure.spec, but previously, spec errors were treated as a boolean — they were so hard to parse, we just looked at code changes to figure out how to make them go away. Now we have a proper guide to exactly what's wrong.

bbrinck22:06:01

Very glad to hear it!

bbrinck22:06:55

@hlship I’d be interested in your feedback on another potential feature: message-fns https://github.com/bhb/expound/pull/96

bbrinck22:06:02

I’m on the fence as to whether this would be useful enough to warrant inclusion

hlship22:06:24

Well, as a library developer, I want expound to be an optional dependency, so I don't think I could use this.

bbrinck22:06:19

Oh sorry, I was unclear in my description on this issue: it works like messages today in the sense that there is no effect if someone isn’t using expound.

bbrinck22:06:44

The only difference is that instead of registering a static string, you can register a function which will be called to generate the string

bbrinck22:06:56

This would let you, say, augment the existing expound error message, or use the problem to craft a custom message. But perhaps in your use case, the static strings are sufficient.

johanatan19:06:28

I don't consider wanting to apply or partially apply a macro to be a "crazy experimentation". It's very much just a thing that advanced [meta-]programmers want to do to keep their code concise.