Fork me on GitHub
#clojure-spec
<
2016-06-08
>
shaunxcode04:06:27

in the cognicast espisode rich mentions a new syntax for default namespaces for keywords in a map - do we know what that looks like yet?

moxaj12:06:21

@alexmiller It's a shame that conform does not (and probably won't) flow into map-of and coll-of 😞. I have a use case where performance is mostly irrelevant and inner values should be conformed. It seems the functionality is there, but instead i'll have to resort to some error-prone, hairy tree transformation. Would you perhaps consider a dynamic var or an optional argument to map-of and coll-of to enable such transformations (at the expense of performance)?

Alex Miller (Clojure team)12:06:00

@moxaj there actually is 'coll-check-limit' now

Alex Miller (Clojure team)12:06:14

Defaults to 100 but you could set it higher

Alex Miller (Clojure team)12:06:45

I'm not sure whether that changes what flows though

moxaj12:06:28

@alexmiller It does not, currently. Just to be clear, only the size restriction is settled on, the flowing stuff might be subject to change?

Alex Miller (Clojure team)13:06:11

Let's just say I'm not the one making the decision :)

richhickey13:06:16

a conforming coll spec would not have the same name - either you are exhaustive/conform or neither, not going to be a dynamic magic thing

dominicm14:06:53

Is there a way to specify in spec, that a key is the focus of my validation, (or where the path of the failure would be), but take other values from the map? Useful for having two fields that match for example.

Alex Miller (Clojure team)14:06:29

you can write a custom predicate that validates anything you like

Alex Miller (Clojure team)14:06:50

like #(= (:foo %) (:bar %))

Alex Miller (Clojure team)14:06:56

not sure if I'm answering your question though

wilkerlucio14:06:50

@dominicm: I guess you want something like (s/and (s/keys :req [::password ::password-confirmation]) #(= (::password %) (::password-confirmation %)))

dominicm14:06:28

@alexmiller @wilkerlucio Do these methods set the paths? So that path would/could be set to [::password-confirmation]

wilkerlucio14:06:06

@dominicm: sorry, I don't understand what you mean by set the paths, can you please clarify?

dominicm14:06:54

@wilkerlucio: In the failure, you get a path when you do a key.

wilkerlucio14:06:20

well, you always have to be composing, so it may depend where the failure is

wilkerlucio14:06:29

if it's a key with a problem, it will point directly to it

wilkerlucio15:06:17

if it's on the last checker (the match one) it will point to the map and say that the fail comes from the map and point the predicate out

wilkerlucio15:06:55

does that answer your question?

wilkerlucio15:06:30

@dominicm: I think is a good idea to try the (explain) on the REPL so you see if the explanations of the errors met your needs

dominicm15:06:48

@wilkerlucio: Yeah. It answers my question. I'm trying to figure out how I can that the predicate after failure, actually comes from ::password-confirmation when doing explain.

dominicm15:06:14

(s/def ::a boolean?)
(s/def ::b boolean?)

(s/def ::thing (s/keys :req [::a ::b]))

(s/explain
  ::thing
  {::a true
   ::b "foo!"})
;; => In: [:foo.core/b] val: "foo!" fails spec...

dominicm15:06:50

I want to be able to set that In for say password validation. So that it's set to ::password-confirmation

dominicm15:06:17

To clarify, I want this:

(s/def ::a boolean?)
(s/def ::b boolean?)

(s/def ::thing (s/and (s/keys :req [::a ::b])
                      #(= (::a %) (::b %))))

(s/explain
  ::thing
  {::a true
   ::b false})
To result in: ;; => => In: [:foo.core/b] val: "foo!" fails spec...

wilkerlucio15:06:22

@dominicm: not sure if there is an easy way to do that, you might get some insight by checking the sources for (s/keys)

dominicm16:06:31

@wilkerlucio: Looks like it works on reification. Fun fun fun.

brabster16:06:01

is there any way to tell what spec was used to conform a value?

brabster16:06:21

I was looking for a way to define a function to transform conformed output

brabster16:06:32

so if I had a way of extracting the keyword that defined the spec I could use a multimethod to do that - probably misthinking it

brabster16:06:07

sure I can find another way, thought it was worth asking anyway. thanks @alexmiller?

nwjsmith16:06:57

@brabster: if you would like to customize a spec's conform function, you can do it by implementing the Spec protocol: https://github.com/nwjsmith/datomic-spec/blob/master/src/datomic_spec.clj#L286-L293

nwjsmith16:06:24

Although I'm not sure that's the best way to go about it, I just wanted my Datomic queries to conform the same whether in map or list form

Alex Miller (Clojure team)16:06:03

I think you're treading on impl details there that can change without warning, fyi

nwjsmith16:06:19

I'm going to try the multi-spec route to see if I can get similar results. Maybe I don't want conformed map and list queries to be the same.

brabster16:06:57

@nwjsmith: thanks for offering up the suggestion anyway 🙂

borkdude18:06:01

We're having a meetup about spec and are wondering about the output in this snippet:

(s/explain-data (s/cat :cat1 keyword?
              :cat2 (s/& (s/cat :cat1 integer?
                                :cat2 integer?)
                         #(> (:cat1 %) (:cat2 %)))
              :cat3 keyword?)
       [:foo 42 43 :baz])

fenton18:06:29

having issues just getting specs going... lein repl complains about spec not being defined

nwjsmith18:06:21

@fenton have you tried lein clean?

nwjsmith18:06:48

What is the output of lein deps :tree?

fenton18:06:11

thank you...i had it over-ridden in ~/.lein/profiles.clj ! doh!

richhickey19:06:29

@borkdude: what are you wondering?

richhickey19:06:21

the fact that it reports the error at :baz ?

richhickey19:06:42

that’s because :cat2 has eaten the ints, then failed its secondary pred. Currently & can’t know that its preds won’t be made happy by some future input, so it’s still working on the & and then can’t eat :baz

richhickey19:06:09

currently regex ops can communicate that they can succeed w/o more input, but not that they are ‘full’, which is what would be required for & to bail out

richhickey19:06:21

that particular case could probably be handled though

dryewo19:06:17

what does (s/cat :a :b) mean? It compiles, but matches only empty lists

dryewo19:06:53

to me it does not seem a valid spec, it should complain

gfredericks19:06:11

yeah the :b seems like the potentially invalid part

dryewo19:06:36

I’m asking because I had smth like (s/cat :url ::url) and I forgot to define ::url above

gfredericks19:06:42

keywords are predicates

gfredericks19:06:48

so it's probably being treated that way

gfredericks19:06:04

but arguably it would be worth not treating them that way since you can't used namespaced keywords as predicates anyhow

gfredericks19:06:43

though if it matches empty lists then I'm probably mistaken about the treating-it-like-a-predicate part

dryewo19:06:44

(s/valid? :a {:a 1}) throws an exception

ikitommi19:06:08

could there be spec-enabled versions of fn and defn?

ikitommi19:06:47

writing a function (defn age-plus1 [{:keys [::age]}] (inc age)) already defines something about it’s inputs, the specs could be extracted from the source?

richhickey20:06:11

@borkdude: next alpha will do this ^

richhickey20:06:15

thanks for the report

angusiguess20:06:27

@ikitommi: If I understand correctly, the reason for fdef and explicit instrumentation is to prevent default instrumentation of functions.

stathissideris20:06:22

@richhickey: In the cognicast you mentioned a bit about the possibility of generating specs because they're data. It's not obvious to me how I would be able to do that, could you point me the right direction?

richhickey20:06:02

they are data as code is data, you’d generate them as you would any other code

stathissideris20:06:19

oh that's what you meant! thanks 🙂

richhickey20:06:17

call s/form on a spec for an example

stathissideris20:06:30

that's very useful... I'm playing with a (simple) heuristic that would produce specs from example data

stathissideris20:06:49

hard to get right, but maybe I can come up with something that's good enough

richhickey20:06:01

specs for spec will help, e.g. the spec for s/cat:

stathissideris20:06:54

are those somewhere in the code already, or still unreleased?

richhickey20:06:13

not yet released

stathissideris20:06:34

but that's the 2-stage generate you mentioned in the cognicast

richhickey20:06:35

I have to go through them with @alexmiller still

richhickey20:06:06

right, that’s for testing mostly. Working from example data is interesting, but not possible in the general case

richhickey20:06:20

testing and parsing I should say

richhickey20:06:43

although now with unform, the toolchains get interesting

stathissideris20:06:27

I'm inspired by F#'s type providers... if they can do it to a useful extent, I think the idea has some mileage

richhickey20:06:40

if not regex-requiring sequences, not so bad. keysets mostly and leaf scalar types

richhickey20:06:24

still you won’t be able to tell required from optional, etc

stathissideris20:06:55

yeah figuring out regexes would take some crazy sequence alignment algorithms (as in biology)

stathissideris20:06:45

well, if a key appears in only half the maps, we can assume it's optional... I'm thinking this could be a cool way to inspect data

richhickey20:06:17

sounds like fun

stathissideris20:06:59

we'll see how far I can take it! Imagine applying this to functions while exercising an existing codebase to figure out signatures.

nwjsmith21:06:20

As I understand from the docs multi-spec is meant for tagged maps, but would be appropriate for specs that apply to either maps or sequences?

nwjsmith21:06:00

An example:

(defmulti query-form (fn [query] (if (map? query) :map :list)))

(defmethod query-form :map [_]
  (s/keys :req-un [::find] :opt-un [::with ::in ::where]))

(defmethod query-form :list [_]
  (s/cat :find (s/cat :find-kw #{:find} :spec ::find-spec)
         :with (s/? (s/cat :with-kw #{:with} :variables (s/+ ::variable)))
         :in (s/? (s/cat :in-kw #{:in} :inputs (s/+ ::input)))
         :where (s/? (s/cat :where-kw #{:where} :clauses (s/+ ::clause)))))

(s/def ::query
  (s/multi-spec query-form (fn [g _] g)))

nwjsmith21:06:59

What do I lose by dropping the re-tagging in multi-spec?

richhickey21:06:26

you can do any discrimination you can with a multimethod - tag keys in maps is just an example. Also note that retag could be polymorphic

sparkofreason21:06:01

Saw an earlier comment that instrumenting protocol implementations doesn't always result in the spec being validated. Will this be supported at some point? Or does it even make sense, i.e. would it be "better" to spec a map with function specs instead of using protocols?

richhickey21:06:01

multi-spec is for data-dependent specs, generally

richhickey21:06:42

spec + protocols tbd. Certainly protocol behind API fn is good practice and supported (spec the API), but many people expose their protocols directly

dryewo21:06:50

is there a way to disallow non-mentioned keys? (s/valid? (s/keys :opt-un [::foo ::bar]) {:foo 1 :baz 2})

nwjsmith21:06:05

An and with a map-of spec and your keys spec will do it I think

dryewo21:06:35

map-of is for homogenous maps

nwjsmith21:06:31

Yes, but you can use map-of's kpred to ensure the keys are the ones you accept

dryewo21:06:15

ah, you mean put a set there?

dryewo21:06:40

indeed, it works

richhickey21:06:44

you can use s/and with s/keys as well

richhickey21:06:31

(s/and (s/keys …) some-further-constraints)

richhickey21:06:52

map-of not nearly as powerful, won’t do per-key validation etc

richhickey21:06:50

map-of is for map-as-collection, keys for map-as-information-object

dryewo21:06:27

now I’m trying to support :opt in this case

richhickey21:06:52

you should always question why you are limiting keys

richhickey21:06:04

just creating brittleness

dryewo21:06:17

well, I’m just exploring the possibilities

richhickey21:06:23

but people keep asking for this, maybe we’ll have a :closed option to keys

dryewo21:06:41

this works:

(s/valid? (s/and (s/keys :req-un [::foo] :opt-un [::bar])
                   (s/map-of #{:foo :bar} nil))
            {:foo 1})

richhickey21:06:25

map-of samples, will not test every key, so a set not a good predicate

dryewo21:06:45

but keys will

seancorfield21:06:18

A use case I can think of is where you might want to limit keys on a map you expose as a data structure outside your code boundary: for example something you convert to JSON to return from a web service or when you insert! into a SQL database (using clojure.java.jdbc for example, where additional columns would lead to an exception). But I’d question whether those are places where you should rely on something like clojure.spec to catch the error (instead of a more explicit approach).

seancorfield21:06:04

We have select-keys for that sort of scenario, right?

richhickey22:06:59

@dryewo: keys is open, so will let other keys through, and map-of won’t necessarily catch them, it just samples for type consistency, not particular values

richhickey22:06:26

you could use #(every? #{:foo :bar} (keys %)) as the second pred

dryewo22:06:26

I tried this: (s/valid? (s/map-of #{:foo :bar} nil) {:foo 1 :baz 2}) and figured that map-of limits possible keys to those that satisfy the set. But you say it’s something that can change in the future?

dryewo22:06:38

the solution with every? looks quite ultimate

dryewo22:06:48

I found *coll-check-limit* in the sources, that explains

richhickey22:06:03

neither coll-of nor map-of do exhaustive checking, nor conforming. We will make the docs clearer

richhickey22:06:23

they are ok for very large collections

dryewo22:06:34

thanks, that makes sense

ghadi22:06:55

i don't want closed sets of keys without a compelling example

ghadi22:06:21

haven't seen one yet

richhickey22:06:24

enough people seem to want exhaustive conforming version so we’ll likely have both

richhickey22:06:50

@ghadi: well, that’s why open is how it is, I agree 🙂

gfredericks22:06:55

the only thing that comes to mind for me is the principle of being conservative about the data you return from an API

richhickey22:06:20

you just engender brittle code on the other end

gfredericks22:06:39

oh if the closedness is publicized then yeah

gfredericks22:06:57

I was just imagining using it for checking that you aren't accidentally leaking extra stuff

richhickey22:06:08

well as @seancorfield said, you can call select-keys

gfredericks22:06:14

but I suppose specs are intended to be publicized

gfredericks22:06:41

cool, I'm on board

richhickey22:06:27

there’s a bit in the podcast interview about change, while maybe not made clear yet, this kind of thing is an important part of the design

gfredericks22:06:01

because it means adding new keys is backwards compatible?

richhickey22:06:02

there are separate semantics for accept/return, but they align with co-contravariance, and the keyset and regex system will let us say when things accept/return compatible things

gfredericks22:06:52

how will predicate equality work in that case? check that the forms are the same?

richhickey22:06:05

i.e. :req less on what you accept is compatible, :req (essentially guaranteeing) more on return compatible, etc

richhickey22:06:03

regexes can be tested to see that they new accepts everything old does (accept) and old accepts everything new does (return)

richhickey22:06:47

base predicates presumed immutable, yes forms

richhickey22:06:40

what I hope people find is that at the fine granularity of spec, things rarely change, and if the do they must compatibly

richhickey22:06:23

and it could get fancier (generative testing to do the non-spec preds)

gfredericks22:06:24

richhickey: I asked on the ML about tactics for moving libraries forward; in the podcast you mentioned something about adding numbers at the end of function names, do you imagine that being a sort of last resort?

gfredericks22:06:46

stu also seemed to mention creating entirely new namespaces, so I'm curious how common you imagine each of those tactics being

richhickey22:06:19

how often do you change the sigs of public fns? I think for algorithmic things rarely and for informational things only in spec-compatible ways. It’s the breaking change that;s the last resort, the numbering of fn names is just being honest with your users

richhickey22:06:51

The problem with libraries is that a lib has 50 fns and when you add 2 new ones it gets a different ‘version’ and no one knows what’s different. What they actually depend upon are the individual fns that they call, not the lib. Stable specs will show the true granularity of change (usually additive)

richhickey22:06:27

I’d much rather deal with foo-2 (which btw can co-exist with old foo) than ‘version 2’ of foo.

richhickey22:06:42

I can move to foo-2 when I please

gfredericks22:06:46

yeah, I definitely appreciate that

gfredericks22:06:29

there are a handful of minor warts in the test.check API that feel hard to improve without breaking

gfredericks22:06:39

for some value of "breaking"

gfredericks22:06:16

at least if "the API is surprising and hard to sort through" is part of the problem, as then adding a foo-2 might maintain those downsides

gfredericks22:06:45

so I suppose I'm having to accept not being able to "fix" things in that way

richhickey22:06:36

for big changes you can also do test.check2

gfredericks22:06:49

as the lib name, or namespace name?

richhickey22:06:11

lib/ns same difference

richhickey22:06:41

then people can move when they please, specs will be in different nses, so they haven’t changed

ghadi22:06:09

test.check2.11

gfredericks22:06:07

richhickey: thanks

richhickey22:06:08

@gfredericks: I would like to be able to convey explanation data through such-that somehow

gfredericks22:06:57

richhickey: alex mentioned something about having a fancier such-that to make the generator for s/and more robust, do you know if that's a different issue from what you just mentioned?

gfredericks22:06:01

(just so I can keep track)

richhickey22:06:06

maybe a (compatible!) extra arg which is a fn to call on failing val to get exception text)

richhickey22:06:00

not sure if that’s what @alexmiller was talking about

gfredericks22:06:17

what he said exactly was: "I think there is some desire to have a better (programmatic) way to have a conversation about filtering with such-that than just boolean yes/no"

richhickey22:06:09

yes, so right now the pred fails and test.check can’t say anything useful because the pred is boolean

ghadi22:06:27

re: open/closed keysets -- I haven't had the need to ensure a closed set, but when working with an API not under my control, it is useful to detect added information

richhickey22:06:37

but spec knows what the conditions were

gfredericks22:06:13

richhickey: okay so he was talking about the same thing? I interpreted his statement to imply some super-fancy way of giving such-that hints about other things to try

richhickey22:06:14

@ghadi: yes, that’s different, and has been requested - (keys-not-in spec map)

richhickey22:06:21

@gfredericks: nope, just better failure reporting, because the failure to gen could be in a nested spec we’d like to at least tell the user which one

ghadi22:06:26

could something along the lines of keys-not-in work at any path? use case is detecting a fully compatible change made on an external API

gfredericks22:06:34

richhickey: after thinking about it a bit, I'm imagining a function that's expected to return a throwable, that way you can customize the error text and the ex-data

gfredericks22:06:53

I guess that means the stack trace starts in a weird place though, which I was trying to avoid :/

kendall.buchanan22:06:26

Big thanks to everyone who contributed to clojure.spec. Spent two days building an internal library on it, and it’s, so, so, so, so, so, so… enjoyable.

bbloom23:06:16

@ghadi: another use case i just ran into in a (non-clojure) project: warning on configuration errors

bbloom23:06:18

had a config.toml file and somebody had a rogue [Section] and a Key = true line was getting put in to the wrong section, so the config setting wasn’t taking hold

bbloom23:06:32

i used a feature of our toml parser to print all the unrecognized keys to the logs