Fork me on GitHub
#clojure-spec
<
2018-03-01
>
clumsyjedi01:03:06

I must be missing something very simple about composing specs

clumsyjedi01:03:09

(s/def ::nums (s/alt :pos pos? :neg neg?))
(s/def ::int int?)
(s/def ::all (s/and ::nums ::int))
(s/valid? ::all 5)
;; => false
(s/explain ::all 5)
;; => val: [:pos 5] fails spec: :myns/int predicate: int?

clumsyjedi01:03:01

so the ::nums spec uses s/conform and changes the value being validated. So ::int is trying to validate [:pos 5] and failing.

clumsyjedi01:03:46

How do I avoid this though? I just want to chain validations together, each validating the original value I pass in

gfredericks01:03:50

I feel like I ran into this recently but can't find any evidence of that

gfredericks01:03:30

I think there's an s/unform or something like that that could be a workaround

gfredericks01:03:35

but that seems like it shouldn't be necessary

gfredericks01:03:50

I know this has been talked about a lot, just don't remember any conclusions

clumsyjedi02:03:56

thanks @gfredericks - It seems like for my actual work scenario I can use s/merge instead of s/and.

clumsyjedi02:03:33

I'm curious what the recommended approach is for the example I pasted though

clumsyjedi02:03:49

I googled around for unform, didn't find much

Alex Miller (Clojure team)03:03:11

It’s best to start s/and with the pred that conforms to itself, particularly a type predicate like int? that will also generate

Alex Miller (Clojure team)03:03:59

You’re probably thinking of the undocumented s/nonconforming wrapper

Alex Miller (Clojure team)03:03:13

Still on the fence about that one

Alex Miller (Clojure team)03:03:13

However I’d just do (s/and ::int ::nums) here

Alex Miller (Clojure team)03:03:15

And s/or is better than s/alt here (although you won’t see any difference in behavior until you combine ::nums with another regex spec

misha14:03:41

I use s/nonconforming quite often (not sure how to feel about this though)

stathissideris15:03:46

here’s a tricky situation: I have a very large spec to describe my very large config. The config contains values in different nested maps that need to be consistent with each other (some have to co-occur, some refer to each other so IDs need to be consistent etc), so in order to validate the config I have extra predicates on the top-level spec to check for consistency. That was great, until I tried generating a config, and of course it wasn’t valid (because it’s very unlikely to get consistency by chance). So I started writing overwriter functions that would be used via fmap in the generator in order to re-align the different parts of the randomly generated config. But it does feel a bit like an uphill struggle. Am I missing something?

Alex Miller (Clojure team)16:03:44

generating large structures with semantic constraints is (inherently) hard

Alex Miller (Clojure team)16:03:06

an alternate approach is to supply a custom generator at the point of use (with stest/check for example) that picks from example configs

Alex Miller (Clojure team)16:03:20

by using a generator on a set of enumerated examples

stathissideris16:03:09

@alexmiller thanks for the insight. I don’t like the idea of giving up the variability that I get from using an actual generator instead of example configs (which I assume you’re implying would he “hand”-written). You’re right, it’s inherently hard… I’ll see if I can refactor a bit to decomplect the constraints between distant parts of the structure, and also see if I can express the ones that cannot be avoided in a more fluent way.

Alex Miller (Clojure team)16:03:28

the general approach if you are using custom generators is to first build a generator for a simpler model, then use gen/bind to create a generator that produces the appropriate structure

stathissideris16:03:55

I’ve done this before, but I think it’s not so applicable in this case, this one has cases where you get a bunch of IDs generated for part of the structure (say metrics collection), and then in another part (for example filter-metric) you need to refer to one of the existing metrics, so the overwriter just replaces the generated filter-metric with one from the IDs in the metrics collection

stathissideris16:03:44

you’d think that a flag on one of the metrics (something like :filter?) would be better… I’m beginning to think the same 🙂

cap10morgan17:03:02

I have a string I want to write a spec for, but it is composed of previously-spec'd string A, followed by a /, and then previously-spec'd string B. They are GitHub "user/repo" strings. If I already have a :github/user spec and a :github/repo spec, how would I write a spec for the "user/repo" strings that composes those first two specs?

Alex Miller (Clojure team)17:03:12

spec is not great for doing composition of string specs like this - I suspect composing the patterns and creating a spec from that will probably end up better

cap10morgan17:03:54

👋 Hey Alex! OK, thanks. I figured it was either something like that or I was missing something really simple. I'll go down the route you suggest. Thanks!

Alex Miller (Clojure team)17:03:41

it’s possible, I just don’t know that you’d be happy with the spec, the generated values, or the conformed values once you were done

misha18:03:46

@stathissideris generator is "just a function", so you can generate a set of ids upfront, and inject those in sub-generators. it will be verbose and might not be pretty, but sometimes you got to do what you got to do...

misha18:03:13

(or change config format, if you control it)

ghadi18:03:46

that's what I do ^

ghadi18:03:02

generate the stuff that you want to "align" first -- then pass them down

misha18:03:57

another option, is to generate sub-structure and explicitly overwrite id. this saves you some code, and sub-generators customizations

misha18:03:58

ofc, "thicker" dependency is – tighter your generators would be coupled, and "just finish it with assoc-in's" would not be enough