Fork me on GitHub
#clojure-spec
<
2016-07-13
>
sparkofreason00:07:45

@alexmiller: Anyway, I'd rather see support for this specific case, which seems common in Clojure, where we have a sequence of keywords followed by associated values. Just supporting the case of having the value validated against the spec named by the keyword would cover a lot of ground, as opposed to having a general dependent spec scheme. There's a nice symmetry with the map behavior then.

Alex Miller (Clojure team)00:07:18

Actually you could do this with keys* couldn't you?

Alex Miller (Clojure team)00:07:42

(s/cat :e any? :av (s/keys* :opt-un [::a]))

seancorfield00:07:02

One thing I’m finding with clojure.spec as applied to user input is that I often seem to need a two-step process: the first is a very basic predicate and a conformer, the second is the real "spec" (and perhaps and conformer).

seancorfield00:07:27

Example, user inputs a country code and I’d like it to be case-insensitive.

seancorfield00:07:48

So the first spec is that it’s a two-character string and the conformer is clojure.string/upper-case; then the second spec actually looks up the (now upper-case) two-character string and verifies it’s a valid ISO country code (and could conform it to country information).

seancorfield00:07:29

Is that somehow a poor use of clojure.spec? Or is there perhaps a better way to deal with input that needs a little "cleanup" before being validated/conformed?

luke00:07:10

@seancorfield hm. If a non-uppercase string is to be considered “valid” input, then I’d say it’s on your country-code lookup function to handle case insensitive input. That seems like it would be easier than having two specs.

luke00:07:21

Unless you want to have a spec for your intermediate data format for some reason.

seancorfield00:07:56

Well, several of the inputs — and country is actually possible here — are sets of strings. For example, ::gender is #{"male" "female"} so it validates and generates nicely, but if you want it to be case-insensitive then you need to do "something" that isn’t necessarily easy with a single spec.

seancorfield00:07:46

(`::country` could be (set (Locale/getISOCountries)) for example)

seancorfield00:07:36

I started leaning toward two specs because internally I wanted ::gender #{:male :female} for the domain model and "appropriate strings" for the input and the conformer there would be (comp keyword str/lower-case) … assuming you can validate the input strings with a different spec.

seancorfield00:07:23

Perhaps I’m making life more complicated for myself by trying to avoid with-gen here...

luke00:07:28

Yeah… two specs isn’t bad if you do want separate models (one for the domain, one for the input) and are ok spec’ing them separately.

luke00:07:46

It is also possible in theory to write a case-insensitive helper, which given a set of strings returns a (generating) spec that accepts any capitalization.

seancorfield00:07:26

Hmm, I’ll have to give it more thought. So far have specs for input fields that conform to the domain model values, and specs for the domain model itself is feeling like the right way to go, as it allows me to generate data for both layers: API (input) and "system" (domain model).

seancorfield00:07:54

(we’re pushing very hard to tease apart what is some rather complected logic in our current codebase — we currently do a sort of transform-validate-update operation on each field and we’ve figured out a nice clean way to separate validate from update — with a view to pushing the updates out to a queue and applying them asynchronously — but that pre-validation transform on a few fields is proving problematic 😸 )

seancorfield00:07:59

Thanks for the input so far @luke

luke00:07:27

yep if you want separate layers, and you’ve fully reified both layers, then specs for each layer seems like the way to go!

luke00:07:55

no hard answers, just what comes off the top of my head when I hear the question 🙂

sparkofreason01:07:21

@alexmiller: Thanks, that works great. Knew there had to be a way to do that, but had my head stuck in regex-land...

sparkofreason01:07:04

Slightly shorter version: (s/cat :e any? :av (s/keys*))

lvh03:07:58

Is there a way to specify “strings that match a particular regex” in a way that produces the correct generator?

lvh03:07:10

(test.chuck I think has that)

lvh03:07:09

it seems like test.check would have a real bad time generating strings until they accidentally match that regex

lvh03:07:24

(that also seems like a general problem, which is why I’m assuming there’s a general soltuion)

gfredericks03:07:39

the test.chuck generator could be moved to test.check proper if somebody wants to rewrite the instaparse regex parser in pure clojure

lvh03:07:10

What is it now? Bunch of Java?

lvh03:07:27

Also hi gfredericks burning the midnight Chicago oil too I see 🙂

lvh03:07:51

I haven’t really read the spec source code any maybe I should just go do that

lvh03:07:07

I’m presuming there’s a way to map predicates to generators

gfredericks03:07:33

lvh: I'm saying the test.chuck code uses instaparse

lvh03:07:39

and I’m guessing that’s why spec/and is not just every-pred

lvh03:07:58

oh, right, and test.check doesn’t get to depend on instaparse

lvh03:07:50

that doesn’t sound very fun

lvh03:07:31

how would you feel about clojure.spec-specific things being added to test.chuck?

lvh03:07:53

say, a spec that understands how to map to generators in test.chuck, like, say, hypothetically, if someone wanted to match a regex 😉

gfredericks03:07:43

I keep thinking I'm going to want a similar clojure.spec utility library

gfredericks03:07:15

clojure.schpec

lvh03:07:03

would depend upon.

lvh03:07:23

I guess I’m screwed anyway because this is a keyword, not a string, so unless there’s something that understands keywords, I’m going to manually assign a generator

gfredericks03:07:03

I get the impression that manually assigning generators is not meant to be too rare

lvh03:07:38

same here

lvh03:07:13

I’ll defer to you since you obviously have a lot more experience here, but just generating samples based on only predicates is going to break really badly really quickly

lvh03:07:50

(basically as soon as the probability of the predicate passing is not very big)

gfredericks03:07:02

yeah I don't think filtering on predicates is expected to be a very useful approach

gfredericks03:07:55

clojure.spec has that behavior in some places because there's not any other reasonable default, except maybe just refusing to try

lvh03:07:33

yeah, fortunatelyi t’s mostly just for dev and test that you do that

lvh03:07:08

so it’s fine if the generator just shrugs and gives up after n consecutive failures

lvh03:07:25

I have used test.check in other directions 🙂

lvh03:07:36

technically still a test I guesS?

lvh03:07:50

specifically, I generated sequences of events/actions on openstack (rackspace public cloud)

lvh03:07:10

to then assert that autoscale (https://github.com/rackerlabs/otter) would still figure a way out

lvh03:07:54

so it found things like “it gets unhappy if there are two expected networks these servers should be attached to and one of them disappears"

Alex Miller (Clojure team)14:07:18

First in a series of spec screencasts ^^

peeja15:07:59

Is it possible to match data against a spec to find the paths at which a particular spec is used?

peeja15:07:30

That is, I want something like conform, but rather than look things up by tag, I want to look things up by spec keyword

peeja15:07:46

(and find the tag-based paths to those parts)

peeja15:07:25

"Find me every Foo in this data structure"

Alex Miller (Clojure team)15:07:54

I’d say no, nothing like that. might help back up to the problem you’re trying to solve though

peeja15:07:59

Yeah, I think I'm actually down the wrong path here. (But it's hard to tell, since I haven't entirely decided on what I'm trying to do.) 🙂

Alex Miller (Clojure team)15:07:51

that’s usually a good first step :)

joshmiller17:07:41

A question I posed originally in #C03S1KBA2: I’m having some trouble understanding how keywords work with respect to clojure.spec… Why is it that (s/def :spec-test-ns/name string?) works, but (s/def (keyword “spec-test-ns” “name”) string?) fails with CompilerException java.lang.AssertionError: Assert failed: k must be namespaced keyword or resolvable symbol? The type of both is clojure.lang.Keyword.

semperos18:07:30

this is the assert that is being tripped @joshmiller : (c/assert (c/and (named? k) (namespace k)) "k must be namespaced keyword or resolvable symbol”)

semperos18:07:21

(I know not fully helpful, sorry)

joshmiller18:07:37

@semperos: Yeah — but (instance? clojure.lang.Named (keyword "spec-test-ns" "id”)) is true and (namespace (keyword "spec-test-ns" "id”)) is "spec-test-ns”, so I don’t understand how that assert is failing.

joshmiller18:07:08

I’m digging into what happens with macroexpand on s/def to see if maybe that’s the issue.

semperos18:07:55

yeah, it’s essentially it’s trying (#'s/named? '(keyword "spec-test-ns" "name”))

semperos18:07:14

and a List is not a named thing, note the in the macro definition of s/def

joshmiller18:07:01

Yeah, I see (clojure.spec/def-impl (quote (keyword "spec-test-ns" "name")) (quote clojure.core/string?) string?) vs (clojure.spec/def-impl (quote :spec-test-ns/name) (quote clojure.core/string?) string?) when doing it the “right” way

Alex Miller (Clojure team)18:07:29

I wanted to let everyone know that I have been continuing to update and extend the spec guide (at http://clojure.org/guides/spec) since the early days and it might have some things you haven’t seen yet in it. I made a list of things that have been added here: https://groups.google.com/d/msg/clojure/fYwDe3ygcm8/JZmmdjsVCQAJ

Alex Miller (Clojure team)18:07:14

I don’t know that hardly anyone has noticed or used the instrument :stub/:replace functionality, which lets you do some pretty interesting things both for example tests or in tandem with check.

joshmiller18:07:29

I ended up being able to dynamically generate the keywords for specs in other namespaces by using a macro where I could unquote the keyword generation, but if anyone knows of another way to go about it, I’d love to hear it.

Oliver George20:07:11

Is there a standard way to add a generator to complement an instance? spec based on a constructor fn. I imagine you would want a spec/gen for each constructor arg.

Oliver George20:07:14

s/instance google.map.LatLng ::lat ::long

Oliver George20:07:50

I think the answer is a combination of s/with-gen, s/gen and gen/fmap

puzzler21:07:21

Watching the screencast about how spec accomplishes multiple things at once got me thinking about how it is difficult to get all the listed benefits simultaneously. For example, if you put your specs in the same namespace as the code, you can't easily make custom generators because it depends on test.check which is generally only a dev dependency. If you put your specs in a separate namespace, you can't rely on predicates defined in your main namespace. I have not yet found a practical way to divide up code involving specs without giving something up. A lot of that has to do with Clojure's limitation/restriction regarding circular dependencies. I hope more info about how to use spec in the context of a real-world project will eventually be provided.

donaldball21:07:41

You can still make custom generators if you restrict yourself to the vars in clojure.spec.gen

mike_ananev21:07:55

Is there any recommendation to put spec and fn's in one file or separate them?

puzzler21:07:45

@donaldball: the guide says the vars in clojure.spec.gen just dynamically link to clojure.test-check, so those vars won't do anything unless you have clojure.test-check listed in your (dev) dependencies. Assuming I understand the guide correctly...

puzzler21:07:58

@donaldball: Or is your point that the code will at least pass compilation, so it doesn't matter if those vars don't actually do anything? (I haven't tried that myself, so I don't know what happens if you don't have test.check active).

puzzler21:07:44

@donaldball: Ah, that's helpful to know.

puzzler21:07:39

@mike1452: Generally, people have been reporting that they have been separating the specs from the implementation, but there are limitations that come with that approach (as far as I can tell).

mike_ananev21:07:33

@puzzler thanx! may be new screencasts from Cognitect will show us a code arrangement with spec.

seancorfield21:07:26

@puzzler: We’re mostly keeping specs separate from code. So our spec ns requires our code ns, and any code that wants the specs applied requires the spec ns (and the code ns if needed). That’s how we’ve solved the circular reference issue.

donaldball22:07:45

Is that mostly a concession to the fact that you’re working in a codebase that needs to support older versions of clojure or an explicit design choice?

donaldball22:07:20

Where I’m playing with specs, and I spec my fns, I tend to like having them adjacent to the fn definitions

seancorfield23:07:01

@donaldball: The bulk of the specs are for layers of our domain model — data — so that’s independent of functions, so it made sense to have a separate namespace for them. And then to put the handful of function specs in there too.

seancorfield23:07:36

And as long as you load the spec namespace into the system before you run the functions that attempt to conform data to those specs, you’re good.

wilkerlucio23:07:56

I'm choosing now to use they adjacent of the code, like donald is doing, I'm not sure if it's the best way but I'm enjoying it so far, will be nice to see the benefits/problems on the long run as people mature specs on their codebases

wilkerlucio23:07:14

for those using on a separeted namespace, what name conventions are you using for the specs namespaces? @seancorfield @puzzler

seancorfield23:07:02

@wilkerlucio: It depends on what we’re spec’ing. We have a set of specs for domain model data (in ns like ws.domain.something where something is the particular domain model "object" being described) and then we have a set of specs for input fields for APIs (in ns like ws.something.field).

seancorfield23:07:12

But it’s early days and we’re moving code around. A lot.

seancorfield23:07:28

We also have structures that tie things together (so we have hash maps that describe the relationship between domain model things, database tables and columns, validation and update routines — using qualified keywords that are resolved to symbols later).

wilkerlucio23:07:53

nice, sounds cool 🙂

wilkerlucio23:07:30

and for specs about functions, are you using something like: for namespace x.y.z have a x.y.z-specs, something like that?

seancorfield23:07:05

No, those specs just go in the namespace for the "something" that those functions would (primarily) operate on. They’re fairly coarse-grained domain concepts (for our systems — "member" covers a lot of ground).