Fork me on GitHub
#clojure-spec
<
2019-10-30
>
ataggart13:10:29

Is there a canonical way to generate only a subset of the “types” from a multi-spec?

sgerguri13:10:43

You can force a multimethod clause to be selected with the following:

(s/gen (<multispec> <dispatch-arg>)))

sgerguri13:10:36

To pick a subset of the multispecs you can make a collection of the valid dispatch args, then randomly sample that.

ataggart13:10:06

Thanks!

👍 4
ataggart14:10:29

Hmm... doing that is yielding maps that have the correct shape for the type, but the value of the dispatching type key is random, not the dispatch value.

ataggart14:10:14

I'm guessing it's because the result of (s/gen ...) isn't flowing through the multi-spec's retag.

sgerguri14:10:56

That all depends on how tight your multispec clauses are. You can override what you get with fmap like this:

(clojure.test.check.generators/fmap #(assoc % ...) (s/gen (<multispec> <dispatch-arg>)))

ataggart14:10:31

>That all depends on how tight your multispec clauses are. What do you mean?

sgerguri14:10:04

For example, I have a multispec where I dispatch on two keys in the map I generate. The keys are unqualified, meaning I can strictly enforce that for a particular clause, I always get a particular value. Like this:

(s/def ::type #{:foo})

(defmethod multispec-fn :foo
  [_] (s/keys :req-un [::type ...]))

sgerguri14:10:25

So even if the s/keys is part of a multispec for a dispatch value that involves :foo, because the dispatch is done based on the value of a function applied to the data this way I make sure that when the above clause is selected, :foo will be the value of the key :type.

sgerguri14:10:49

In general though, if you do the fmap above it should be fine.

sgerguri14:10:10

Irrespective of whether you do something similar to what I just described or not.

ataggart14:10:08

Using the code here: https://clojure.org/guides/spec#_multi_spec

user=> (map :event/type (gen/sample (s/gen :event/event)))
(:event/error :event/search :event/search :event/search :event/error :event/search :event/error :event/search :event/search :event/error)

user=> (map :event/type (gen/sample (s/gen (event-type {:event/type :event/search}))))
(:+/- :*/. :*/gq :IZ/K :./.35 :YT/* :d/B :*Pk/q2L6 :Lf/J! :+/M-)

ataggart15:10:28

@alexmiller Continuing the multi-spec example from the official guide, does this seem reasonable for the simple case of a kw retag?

ataggart14:10:49

The intention of the second one is to only yield :event/search.

ataggart14:10:25

That's why I think just calling the multi-method isn't enough. Generating via the multi-spec would flow through retag, but the code above doesn't.

ataggart14:10:29

The master approaches...

Alex Miller (Clojure team)14:10:44

I think going back to your original question, the answer is no, there is no way to (easily) generate a subset of the multispec values right now

sgerguri14:10:06

If you fmap, however, you should still get the right thing.

ataggart14:10:10

Man, I was all braced to receive your wisdom, Alex.

Alex Miller (Clojure team)14:10:15

there are a variety of hacks :)

ataggart14:10:19

Yeah, I can manually retag

Alex Miller (Clojure team)14:10:01

you can choose a particular one and retag, or you can dive into the guts of the multimethod dispatch value map (which is what the multispec generator does)

sgerguri14:10:15

Also :event/type is defined as a keyword in the example, whereas in mine above I restrict that particular spec to a single value.

Alex Miller (Clojure team)14:10:34

or you could s/and and only take events of a particular type

sgerguri14:10:41

It just so happens that I have the same unqualified key in all the multispecs defined difrerently per multispec.

ataggart14:10:04

Every time I try to use s/and for this issue I end up with such-that errors

sgerguri14:10:23

s/with-gen is your friend there.

sgerguri14:10:46

Don't be afraid to use it - I do in strategic places, typically around complex s/and cases.

ataggart14:10:56

problem is I can't tell what's causing the such-that error

ataggart14:10:14

Well, I can eventually, but it's not obvious when it happens

ataggart14:10:28

ok, I'm going to beat my head on this a little more

sgerguri14:10:31

Well if it's an s/and then simply provide your own generator by wrapping that in s/with-gen.

ataggart14:10:49

The issue tends to be using s/and when I should use s/merge

sgerguri14:10:28

Possibly. Though if you rely on custom conformers, s/and is a very frequent thing to end up using.

ataggart14:10:51

yep, just wasn't helping in this case

ataggart14:10:06

retagging via fmap over the generated values from the multimethod is working though.

👍 4
ataggart14:10:50

Found the such-that:

user=> (s/gen :event/event)
#clojure.test.check.generators.Generator{:gen #function[clojure.test.check.generators/such-that/fn--6656]}

orestis15:10:39

I know it's a vague question, but what's the upgrade path from spec -> spec2 look like? I know there's changes coming in s/keys and I guess the functions that validate/conform/explain might change, but I was wondering how much of the spec definitions will change?

orestis15:10:08

I'm trying to figure out if we should start with spec2 directly, and deal with the breakage as it comes (I guess it's pre-alpha), or start with spec and deal with the upgrade breakage when it comes.

seancorfield17:10:46

We've been trying to keep a branch of our codebase at work up to date on Spec 2 but it's required quite a few changes to some of our Spec 1 code and at this point, we've essentially "given up" on a straightforward migration. We plan to adopt Spec 2 for new code once it is out of pre-alpha status and run a mixed Spec 1 / Spec 2 codebase for a good long while.

Alex Miller (Clojure team)15:10:20

I would use spec right now

Alex Miller (Clojure team)15:10:50

the api functions - validate/conform/explain etc have gained some capabilities, but I expect will continue to work in spec 2 as is

mpenet15:10:31

any news on eta? we're also waiting on v2 release for some new features (looking in particular at new data spec format for "interpretation" of specs)

Alex Miller (Clojure team)15:10:34

the spec op forms will mostly be the same, although keys -> schema/select will be a significant change and there are some differences around the use of sets, preds, functions, etc at thee top level

Alex Miller (Clojure team)15:10:05

I don't have an eta but I'd guess it's on the order of months

ataggart15:10:51

heh, ok, thanks

orestis16:10:22

Thanks for your reply Alex, much appreciated.

seancorfield17:10:46

We've been trying to keep a branch of our codebase at work up to date on Spec 2 but it's required quite a few changes to some of our Spec 1 code and at this point, we've essentially "given up" on a straightforward migration. We plan to adopt Spec 2 for new code once it is out of pre-alpha status and run a mixed Spec 1 / Spec 2 codebase for a good long while.

seancorfield17:10:33

@orestis As Alex says, use Spec 1 for the time being. Conversion to Spec 2 may be easy or hard depending on how you use Spec 1.

orestis17:10:15

The thing is I need to do closed map checking, recursively. I think that it can be hacked on top of spec1, but not sure if it’s already done and if not, if worth the effort to do myself.

seancorfield17:10:32

Is the project a production-level one, or likely to be one within the next couple of months?

seancorfield17:10:46

If not, I'd say give Spec 2 a go. Otherwise, stick to Spec 1.

Alex Miller (Clojure team)17:10:51

I wouldn't, I'd say stick to spec 1 :)

Alex Miller (Clojure team)17:10:47

there is no release of spec 2 available, because I do not think it is release-worthy yet. until then, I would not recommend for anything other than tire-kicking and feedback (which is very welcome)

seancorfield17:10:08

Right, that's what I meant about not production-level -- if this is just an exercise project, go kick the tires. Otherwise, stick to Spec 1.

seancorfield17:10:52

I really like where Spec 2 is going but it's a different beast in many ways from Spec 1...

orestis18:10:38

Production-level. Ok, I’m hearing you loud and clear :)

mloughlin20:10:51

The book Secure By Design from Manning writes about using Java classes with immutable properties and data contracts in the constructor, then only using this defined class (instead of using, say, a string for a bank account number) in order to prevent business logic bugs. The nice thing about this is the type system enforces a degree of defensive programming. My question: is there an clojure.spec analogue, or convention for this?

Alex Miller (Clojure team)21:10:50

mostly I think that's trying to patch problems that are endemic to use of Java, rather than an actually good idea. however, I'd say the closest thing is to separate specs into two levels - domain definitions (::domain/bank-acct-no) and uses in specific contexts that alias those domain definitions

Alex Miller (Clojure team)21:10:30

but this is really mostly just making complicated definitions once rather than repeating them

mloughlin22:10:43

would you validate as a pre-condition every time you were to use ::domain/bank-acct-no? I suppose it's super cheap computationally

Alex Miller (Clojure team)22:10:52

I would use instrument to check during dev/test

mloughlin22:10:19

thanks for clarifying