Fork me on GitHub
#clojure-spec
<
2017-01-19
>
qqq00:01:17

somethign else tht would be nice in spec would be to gnerrate recursive structures breadth first, and when we hit a certain node count, only take non recursive branches

qqq00:01:39

is there a spec option to limit the size of coll-of ?

qqq00:01:40

where is the code which shows how coll-of are generated?

qqq00:01:06

I'm looking at github/clojure/clojure, which sees to show checking of coll-of, but not generating of coll-of

Alex Miller (Clojure team)00:01:04

:gen-max is the option to limit coll-of

qqq02:01:47

does clojure spec support things like parameterized spec? I want to be able ot say things like "this is a ::tree-of ::widget" and "this is a ::tree-of ::gadget" instead of havint to define ::tree-of-widget and ::tree-of-gadget (or one for each 'type')

Alex Miller (Clojure team)03:01:09

you can however use s/multi-spec to create an “open” thing in the middle

seancorfield03:01:34

Ah yes, I need to into multi-spec some day 🙂

qqq03:01:21

@alexmiller: I have no idea what this multi-spec technique is. Can you point me at a tutorial?

Alex Miller (Clojure team)03:01:21

but basically you use a multimethod to choose which spec to use based on the data

Roman Liutikov12:01:22

This question probably was raised already. I’ve played with spec in ClojureScript a little, created a spec for application state for validation purpose. But I found it annoying to use fully-qualified keys with maps, because it requires the namespace whenever I need to get or destruct a map. This is what stopping me from using spec in production code. Am I missing something? I feel like there should be a reasonable explanation for this.

gfredericks12:01:45

@roman01la there are some new syntaxes for namespaced keywords

Roman Liutikov12:01:46

@gfredericks do you mean namespace alias? I’m using them.

gfredericks12:01:21

for constructing maps whose keys all have the same namespace

gfredericks12:01:41

also {::keys [foo bar baz]} I think

gfredericks12:01:48

when destructuring

gfredericks12:01:19

the map syntax is #:foo{:bar 12} and #::baz{:bang 12} I think

gfredericks12:01:35

you should see it at the repl when it prints a map with namespaced keys

rickmoynihan12:01:16

@roman01la: Not sure if this is what you mean by "require" you don’t need to :require and load a namespace to use keys that are fully-qualified

gfredericks12:01:02

but you do to alias

rickmoynihan12:01:59

roman01la: also not sure if you know but s/keys has :req-un and :opt-un to let you bridge between unnamespaced keys in maps and namespaced keys in specs…

Roman Liutikov12:01:31

@gfredericks that’s great, thanks! #::baz{:bang 12} doesn’t work in ClojureScript, probably not implemented yet

gfredericks12:01:33

@roman01la: if the clojurescript compiler runs in clojure 1.9 does it work?

Roman Liutikov12:01:33

@gfredericks didn’t try it on 1.9 yet

gfredericks12:01:13

if cljs still uses clojure's reader then that'd be a prereq for sure

Roman Liutikov12:01:39

@gfredericks tried with 1.9.0-alpha14, still doesn’t work. I guess ClojureScript has it’s own reader. Anyway, thanks for sharing the new syntax, this will save me some time!

pyr12:01:40

@mpenet can you expand on what you mean by "One of the thing I like is togglable asserts in pre/post" ?

mpenet13:01:12

@pyr using :pre/:post in fn definitions, mostly s/valid? calls. and having the abitily to switch it on/off depending on assert value at compile time

mpenet13:01:03

ex with a profile with :global-vars {assert false} for prod

mpenet13:01:28

you can do the same with s/assert, inline, but I find pre/post kind of nice for this stuff

mpenet13:01:45

s/assert has the advantage that you can toggle in on/off at runtime

mpenet13:01:18

all of this is handy for the cases when instrumentation is either too intrusive or when you want to actually run this stuff in prod

pyr13:01:42

@mpenet thanks, i'll play with that later today to see how I fare

pyr13:01:23

I didn't know :pre/:post validation asserts could be en/disabled at compile time

mpenet14:01:40

I found that old post talking about it, old stuff 🙂 http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

pyr14:01:03

I was reading that yes, still first google link to show up 🙂

mpenet14:01:02

I guess it could make it relatively easy to write some sugar on top of it to have Schema like defn with annotations.

mpenet14:01:27

But personally I am good with what we have

pyr14:01:49

yes, this is also enough for my needs

owen15:01:41

is there a good way to test that an fdef with a generator will throw an exception somewhere? I'm not having success

Alex Miller (Clojure team)15:01:28

can you expand that question? not understanding it

owen16:01:55

Sure thanks Alex. I'm testing a function with stest/check, an fdef and a generator that is guaranteed to cause an exception. I can't figure out what :ret I can use in the fdef to check for the exception

Alex Miller (Clojure team)16:01:53

:ret is for non-exceptional returns

Alex Miller (Clojure team)16:01:10

there is no way to spec exceptions

owen16:01:46

Ah ok makes sense then

ddellacosta16:01:00

what is the rationale behind clojure.spec/or returning map pairs? I was hoping to be able to use it to validate what is essentially a sum type, but seems like I will have to do more processing to handle that. I guess my other question is, is there anything suitable for validating a sum type (i.e, simply has a bunch of predicates, and is valid if one of the predicates returns true)?

ddellacosta16:01:11

happy to get pointed to docs; apologies if this has been asked many times here

ddellacosta16:01:37

I do see the doc-string says > Returns a destructuring spec that returns a map entry containing the key of the first matching pred and the corresponding value. Thus the 'key' and 'val' functions can be used to refer generically to the components of the tagged return.

ddellacosta16:01:00

I sense the rationale is lurking in there, but I suppose this speaks to my larger ignorance of how to use spec idiomatically

ddellacosta16:01:24

something something use maps more something something

joshjones16:01:14

Maybe I’m oversimplifying your question @ddellacosta , but when you conform using s/or, how would you know which predicate matched without some way to identify it?

Alex Miller (Clojure team)16:01:04

the idea is that the tag is used to identify how spec parsed the value (and also used to identify problems in explain errors)

ddellacosta16:01:22

@joshjones why do I care if it’s a valid value of one of the types I’m expecting?

ddellacosta16:01:42

@alexmiller I guess I don’t see how to easily use that when composing it with another spec

Alex Miller (Clojure team)16:01:00

many specs conform to a structure that describes how they were parsed

Alex Miller (Clojure team)16:01:08

all of the regex specs, etc

ddellacosta16:01:28

yeah, there seems to be something basic I’m missing here

Alex Miller (Clojure team)16:01:43

two options for not having this issue specifically with s/or...

Alex Miller (Clojure team)16:01:46

1. write a function that accepts all the options, so instead of (s/or :e even? :p pos?), do #(or (even? %) (pos? %))

Alex Miller (Clojure team)16:01:11

that makes the choice opaque (which may be good or bad depending on your needs)

Alex Miller (Clojure team)16:01:12

2. use the currently undocumented and possibly to-be-removed spec s/nonconforming: (s/nonconforming (s/or :e even? :p pos?))

Alex Miller (Clojure team)16:01:13

if you just want to check whether a value is valid, use s/valid?. if you want to know why it’s valid, use s/conform. If you want to know why it’s invalid, use s/explain.

ddellacosta16:01:05

@alexmiller thanks for all of that

ddellacosta16:01:40

I guess part of the problem is that I’m using s/conform and expecting it to more or less simply return the value I’m testing, except in specific cases (changing vector to set, in this case I think)

ddellacosta16:01:56

and part of that is maybe that I don’t yet understand the use-case for s/conform

ddellacosta16:01:12

and how one would use something like s/or effectively

ddellacosta16:01:17

within that context I mean

Alex Miller (Clojure team)16:01:25

yeah, s/conform returns a different value than the original value specifically to tell you why/how it’s valid

Alex Miller (Clojure team)16:01:39

if you don’t care, don’t use conform

ddellacosta16:01:51

right, okay…that is a helpful way to think about it

Alex Miller (Clojure team)16:01:20

note also that you can use s/unform to retrieve the original value from the conformed value too

ddellacosta16:01:24

I guess thinking about it a bit more, I see two distinct possible values that could be returned from conform: one is the value coerced to a new value, and the other has added spec metadata —it seems to me right now that s/conform conflates these two somewhat

ddellacosta16:01:31

for example, converting a vector to a set is the former

ddellacosta16:01:40

whereas the output of s/or is the latter

ddellacosta16:01:12

am I off here? I still feel like I don’t get the broader implications of spec, or how we might be able to use it

seancorfield17:01:03

(if (s/valid? ::my-spec some-value) some-value (handle-error (s/explain-data ::my-spec some-value))) seems to be what you’re looking for @ddellacosta ?

seancorfield17:01:19

(or some similarly structured setup)

seancorfield17:01:50

We use both s/valid? and s/conform — we use the latter when we want a result that “conforms” to a spec but may start off slightly differently (which we use for our API inputs, to produce domain model values). And as Alex said, when we don’t care about any potential change in the data, we use the former.

ddellacosta17:01:09

@seancorfield thanks, will see if that does it. Per your second point, ideally I’d like to use s/conform in the same way—but if s/conform returns data that has been changed to represent how a value conforms to a spec, doesn’t that defeat the purpose?

seancorfield17:01:15

Well, it will only do so under the rules of the spec itself.

lmergen17:01:37

@seancorfield: i would be very interested to learn how you use spec in real world projects

lmergen17:01:51

we're introducing it right now, running alpha 14

lmergen17:01:09

and it's a bit tough to find the right "pattern" that makes sense to us

seancorfield17:01:45

For example, we have API input specs that accept either a date, or a string that can be parsed to a date using two allowed formats. s/valid? on such a string will just return true (and we still have a string; or false for an illegal string). s/conform on such a string will return a date (or ::s/invalid for an illegal string).

joshjones17:01:49

so in this case is it accurate to say that you are using spec to actually transform the data? (string -> date)

seancorfield17:01:05

Another use case: we allow members to search within specific distances (dating site). If they are a subscriber, they can specify distances of 20, 30, 40, 50. All members can specify distances of 150, 500, 1000, and -1 (”any”). We use a spec with s/or to get back either [:basic distance] or [:platinum distance] so we can validate both the actual value and which category it belongs to.

seancorfield17:01:10

@joshjones Yes, some coercions. As Alex often cautions, that conforming / coercing means all clients of those specs get the coercion if they use s/conform but in this case that’s what we want.

ddellacosta17:01:14

to me that seems like a spec is somewhere in between a value and a statement about a value

ddellacosta17:01:20

I find this pretty confusing, fundamentally

ddellacosta17:01:39

seems like it makes s/conform of limited use

ddellacosta17:01:49

or at least, limited in the case of using s/or

joshjones17:01:23

yes yesterday @alexmiller said: “spec works best when you primarily use it to speak truthful, assertive statements about what your data is and not to transform your data or make statements about what your data is not” https://clojurians.slack.com/archives/clojure-spec/p1484765212007116 https://clojurians.slack.com/archives/clojure-spec/p1484765279007117

ddellacosta17:01:53

again, I’m talking about s/conform here @joshjones

ddellacosta17:01:13

and it seems to violate exactly what those comments are saying, in fact

seancorfield17:01:17

If you need to know which “path” of conformance your value takes, s/or is very useful. Since otherwise you’d need to code up both the validation logic and the parsing logic in order to determine that.

joshjones17:01:21

I’m not referring to anything you’re talking about @ddellacosta , only to what @seancorfield said about his use of spec

ddellacosta17:01:33

@joshjones ah, apologies—misunderstood

joshjones17:01:54

no problem, I am also seeking a better understanding of best practice :thumbsup::skin-tone-2:

ddellacosta17:01:44

yeah, along those lines this is all really helpful—thanks for linking to those comments @joshjones

seancorfield17:01:58

It’s taken us a while to settle on this approach — given that Alex has repeatedly cautioned against specs that coerce data 🙂 — and we also have the added complication that for a given API, some of our input parameters must be looked up and “conformed” dynamically (i.e., we have a database table listing possible inputs and each row identifies a spec that should be applied). So we can’t just write static specs for input arguments, we have to have somewhat generic specs and conform each input argument based on a lookup.

seancorfield17:01:07

(and we’re still wading through cleaning that up)

seancorfield17:01:50

We could probably simplify this by dynamically generating specs directly from our metadata at this point — and we may well do so.

ddellacosta17:01:05

so question for you, @seancorfield —why not simply write something explicit that does not rely on spec to add the metadata about your distance (platinum vs. basic)? It seems to me that this is a value, not a spec.

ddellacosta17:01:31

I’m asking selfishly to clarify my understanding of spec, to see how you are thinking about this, as I still don’t fully get it

seancorfield17:01:37

Because using spec allows us to remove a lot of logic that we had before.

seancorfield17:01:44

(s/def ::platinum-distance #{20 30 40 50})
(s/def ::basic-distance    #{150 500 1000 -1})
(s/def ::distance (api-spec ->long (s/or :basic ::basic-distance
                                         :platinum ::platinum-distance)))
(and the api-spec macro) replaces a bunch of explicit code that attempted to coerce string to number and validate it and categorize it as basic vs platinum. Now we can just have (let [[level distance] (s/conform ::distance input)] …) and we’re done.

seancorfield17:01:38

Having the declarative specs means we can show those to business and they can “read the code” without having to actually read real code full of conditionals and transformations.

seancorfield17:01:30

We can also have the business rules in one place (for the most part) and they drive not only our actual business logic but can also be used to generate test data (and test functions extensively).

ddellacosta18:01:08

okay, I’ll have to think about that a bit. Thanks a lot @seancorfield , this has been very helpful

scriptor20:01:40

how would one spec varargs?

scriptor20:01:32

could I just use the vararg's name and use coll-of?

schmee20:01:18

scriptor I think the idea is to spec the args as a sequence with spec/cat and use spec/? for the optional args

joshjones20:01:11

@scriptor yes, compose the var args the same way you’d compose any sequence.. only it doesn’t have to be coll-of … can be k/v pairs, any sequential spec. for example:

(s/def ::someopt nat-int?)
(s/def ::vargs (s/cat :optargs (s/keys* :opt-un [::someopt])))

(defn myfunc [& optargs] nil)
(s/fdef myfunc
        :args ::vargs)
(stest/instrument `myfunc)
(myfunc :someopt 55 :otheropt 99)

joshjones20:01:58

can be a coll-of, use spec regexes, anything … the above just shows a common use case for var args: k/v pairs in a sequence (as opposed to a map)

scriptor20:01:37

so :rest indicates the following spec is for the optargs parameter?

scriptor20:01:53

well, not indicates, you're just using the name :rest

joshjones21:01:12

yes, should probably have called that optargs .. will change it for clarity

joshjones21:01:15

@scriptor I need to clarify something after doing another example — my example above works but I wanted to use another common case because I was wrong about just using coll-of for cases where you have one required and then an optional argument. for example:

(s/def ::vargs (s/cat :req string? 
                      :optargs (s/* int?)))

joshjones21:01:50

if you use coll-of for :optargs it will not work because you’re giving it multiple items, not an actual collection

scriptor21:01:41

ah, interesting, I guess it's because of the regexy nature of s/cat

joshjones21:01:27

well, s/cat gives the actual argument vector spec. but inside that, you then have several elements, and one of those is not a collection if it’s for example: [”abc” 42 43 44]

scriptor21:01:29

the s/* greedily consumes the rest of the arglist, which as you said is multiple separate items and not a single collection

joshjones21:01:44

what you have there is a string, an int, an int, and an int .. no collections

joshjones21:01:08

yes that’s correct. i feel my answer above was misleading so i wanted to clarify

joshjones21:01:46

another example that will match (fn [s & opts]):

[”abc”]
[”abc” 3/4]
[”abc” 3/4 42.9]
[”abc” 3/4 33.3 10.5 99.9]

(s/def ::vargs
  (s/cat :req string? 
         :optargs (s/? (s/alt :ratioonly ratio? 
                              :ratio-double (s/cat :s ratio?
                                                   :i (s/+ double?))))))

joshjones21:01:27

wanted to show that the optional argument portion of the sequence can be more than just a simple homogeneous concat of elements

Alex Miller (Clojure team)21:01:48

you should really use s/keys* for this - that is it’s reason for existence

Alex Miller (Clojure team)21:01:20

(s/def ::varargs (s/cat :req string? (s/keys* :opt-un [::ratioonly ::ratio-double]))) etc

joshjones22:01:30

@alexmiller that was in my first example above — but doesn’t that require k / v pairs?

Alex Miller (Clojure team)22:01:27

yes, isn’t that what you’re specing?

Alex Miller (Clojure team)22:01:49

oh, I see you have a :ratio-double with multiple vals

joshjones22:01:52

no, in this case i was showing a spec that would match all of the following:

(myfunc “required string”)
(myfunc “required string” 4/3)
(myfunc “required string” 4/3 33.2 99.9)

Alex Miller (Clojure team)22:01:03

I’m going to go out on a limb and say that’s weird :)

Alex Miller (Clojure team)22:01:15

and you shouldn’t write a function signature that way in the first place :)

joshjones22:01:54

yes it is — my first example showed what you said — i was just showing how the optional arguments portion of a function need not be a homogeneous group of items .. not endorsing it, just demonstrating the versatility 🙂

joshjones22:01:28

saying “hey, if it can do this, it can definitely do something much simpler” :thumbsup::skin-tone-2:

gdeer8122:01:56

is there a way to do multiple specs at once like you have a bunch of things that should be a string so instead of doing (s/def ::foo string?) (s/def ::bar string?) you can just say [::foo ::bar :baz ::fname ::lname ::email] string?

bbloom22:01:51

has somebody done a richer explain function yet? i find that when you have some ors or alts w/ larger maps involved, the output becomes unwieldy

Alex Miller (Clojure team)23:01:18

@bbloom Drogalis started porting some of his onyx stuff to work over spec explain-data (and using fipp) - really more of a poc stage

Alex Miller (Clojure team)23:01:36

I actually picked that up and hacked on it a bit but found that explain-printers are missing some crucial data

Alex Miller (Clojure team)23:01:43

namely, the original root value and root spec

Alex Miller (Clojure team)23:01:59

that’s missing from the explain-data map

Alex Miller (Clojure team)23:01:09

I have a pending enhancement + patch to add that

bbloom23:01:14

ah yes, those things and the immediate parent or some chain of anscestors should be passed down

bbloom23:01:23

this way explainers can be context sensitive

Alex Miller (Clojure team)23:01:29

everything else is discoverable within the explain-data path

bbloom23:01:40

ah right, can just pop on the path

bbloom23:01:42

that makes sense

Alex Miller (Clojure team)23:01:48

but you don’t have the root value

bbloom23:01:52

(get-in root (pop path))

bbloom23:01:02

gotcha. well looking forward to that 🙂

bbloom23:01:43

this brings up one other issue that i’m running in to now

michaeldrogalis23:01:43

Heh. Never did get around to finishing that.

bbloom23:01:49

in fact, it’s one that i ran in to in the past

Alex Miller (Clojure team)23:01:51

I’ve done some more work on it

Alex Miller (Clojure team)23:01:06

but not in a public repo

Alex Miller (Clojure team)23:01:40

I’ve shelved it for the moment until the patch above gets through as then it will be easier to demo

bbloom23:01:59

i’m running in to a situation now where i have a recursive spec that is giving me useless explain output b/c it’s producing LOTS of output

bbloom23:01:05

b/c the recursive spec fails to match

bbloom23:01:46

one thing you could do with explain-data is find the CLOSEST match

michaeldrogalis23:01:46

@alexmiller Curious as to how you attempted it. Took me ages to do it with Schema, not really looking forward to trying it again with Spec.

Alex Miller (Clojure team)23:01:52

I seem to recall Colin made something similar in the macro grammar stuff he did

bbloom23:01:13

yeah, it’s kinda critical i think

bbloom23:01:37

otherwise spec falls down on ASTs and other recursive structures after data gets too big

Alex Miller (Clojure team)23:01:39

@michaeldrogalis well, I mostly brought it up to more recent spec and extended it just slightly further

bbloom23:01:41

at least explain does

bbloom23:01:36

unfortunately the literature on error detection and recovery in parsers seems to be kinda ad-hoc

Alex Miller (Clojure team)23:01:39

@bbloom I also have a patch that sorts the longest errors first, which generally is a good strategy

bbloom23:01:48

yeah, that’s a good start

Alex Miller (Clojure team)23:01:50

except with recursion when you probably want the reverse :)

bbloom23:01:00

is that longest path to an error?

bbloom23:01:07

or just BIGGEST error?

bbloom23:01:55

i asked about this on twitter a while ago in the context of text parsing

bbloom23:01:41

chapter 10 is about error handling, which may be worth studying

bbloom23:01:54

there seems to be some attempt at a sensible system for producing good error messages

Alex Miller (Clojure team)23:01:02

I think cleaning up fanout is another big area for improvement

Alex Miller (Clojure team)23:01:38

For removing repetition

bbloom23:01:52

you mean like representing the output as a tree?

Alex Miller (Clojure team)23:01:18

Nah, just reporting all errors at common path with single context

Alex Miller (Clojure team)23:01:29

An ide might do a tree though

bbloom23:01:39

another idea is a sort of two column view

bbloom23:01:48

ie pretty print (or just indent) on the left

bbloom23:01:55

and messages on the right

Alex Miller (Clojure team)23:01:00

I suspect recursion will just suck regardless :)

bbloom23:01:11

yeah, well recursion always sucks when it comes to printing stuff 🙂

bbloom23:01:22

as a man who does a lot of recursion and a lot of printing, believe me, i know

bbloom23:01:47

whatever happened to that data structure visualizer? is that still built in? lol

Alex Miller (Clojure team)23:01:10

it still doesn’t work very well :)

bbloom23:01:23

yeah, that’s the thing about text based UIs

bbloom23:01:27

when they are bad, they are kinda bad

bbloom23:01:35

when they are bad, THEY ARE VERY BAD