Fork me on GitHub
#clojure-spec
<
2016-12-06
>
settinghead02:12:04

not sure if this have been asked before, suppose i have 2 cases: ["a" 1 true "b" 2 false "c" 3 true] and [["a" 1 true] ["b" 2 false] ["c" 3 true]], my understanding is (s/+ (s/*cat :first string? :second number? :third bool?)) will validate the first case. my question: how to write spec for the second case where each group is enclosed in a vector/list?

hiredman03:12:46

(s/* (spec (s/cat :first string? :second number? :third bool?))

hiredman03:12:20

s/spec allows spec to distinguish between the levels of sequence matching

Alex Miller (Clojure team)05:12:18

another alternative would be (s/coll-of (s/tuple string? number? boolean?) :kind vector?)

Alex Miller (Clojure team)05:12:21

I’d probably prefer the non-regex version in this case

gfredericks16:12:48

Quoting Mr. Miller on clojure-dev:

gfredericks16:12:53

> That said, current versions of those libs still exist for those wanting it and we could release point updates to those if needed later.

gfredericks16:12:35

⇑ something rich didn't address in the criticism of semantic versioning -- sometimes a one-dimensional version-space is insufficient

gfredericks16:12:03

maybe appending things to a timestamp-based versioning system would be good enough

gfredericks16:12:27

version 201612061011.2

gfredericks16:12:28

@alexmiller I'm curious what alternative you have in mind

Alex Miller (Clojure team)16:12:42

I don’t understand what problem you’re trying to solve

gfredericks16:12:45

@alexmiller making a "point release" off of an older version -- they exact use case you were describing in the email I quoted

Alex Miller (Clojure team)16:12:20

I don’t see a problem with making a point release off of an older version?

gfredericks16:12:28

in semantic versioning it's clear what's going on

gfredericks16:12:08

if we upgrade from 0.8.0 to 0.9.0, but then want to add bugfixes for users stuck on 0.8.0, we release 0.8.1 and nobody thinks that 0.8.1 is a better version of 0.9.0

gfredericks16:12:16

but if our versions are just timestamps, there's more confusion

gfredericks16:12:34

relatively clear -- the non-linearity of the releases is clearer

Alex Miller (Clojure team)16:12:44

I don't think Rich was saying that we should stop using versions

gfredericks16:12:06

he spent a while criticizing the semantics of semantic versioning, and had a slide that suggested "YYYYMMDDHHMM" or something similar as an alternative

gfredericks16:12:57

and I was thinking about how that sort of versioning system would need some sort of additional feature to support making changes for older releases

gfredericks16:12:49

I'm probably communicating this poorly

naomarik16:12:44

maybe something like clojure el-capitan 201611221245

naomarik16:12:12

attach a name to a major version

gfredericks16:12:59

the idea of a "major version" was one of the criticisms

naomarik17:12:19

i think it was cause the number is meaningless… the name is as well but it’s easier to think of a feature set of android nougat vs kitkat than to refer to them as their version numbers

naomarik17:12:09

and i think the lack of a clear alternative was to spawn discussions like this

donaldball17:12:49

I took much of the point to be that you would ship both old and new apis in your library (assuming you didn’t choose to fork to a new library name when you changed apis). Thus if you needed to augment the old api with a new feature, you’d release a new version of the whole library.

sparkofreason17:12:33

Is it possible to attach a generator to a predicate without defining an explicit spec? For example, I want to make a predicate queue?, and generate instances of clojure.lang.PersistentQueue from a spec like (s/coll-of int? :kind queue?), similar to how it works for vector?.

gfredericks17:12:21

@donaldball I'm not sure that would work in the case I brought up, where your library requires clojure 1.8 but you want to augment older versions for users that haven't upgraded to 1.8 yet

sparkofreason18:12:15

@gfredericks Certainly doesn't look like it. clojure.spec.gen/gen-builtins provides a nice shortcut for predicates in clojure.core. It would be nice if this was extensible.

Alex Miller (Clojure team)18:12:08

I suspect making that extensible might also invite a world of conflicts

kenny18:12:59

@alexmiller Why was that done for the predicates in clojure.core then? Is the purpose of this purely for backward compatibility? It seems like the general workflow is write your predicate, write a spec that is defined as a call to that function, include a generator on that spec, and use that spec but clojure.core varies from this workflow.

kenny18:12:07

It almost seems like you don’t even need predicate functions at all with spec.

kenny18:12:26

A call to clojure.spec/valid? could entirely replace predicates

kenny18:12:33

I think.. 🙂

Alex Miller (Clojure team)18:12:16

That doesn't make any sense to me :)

Alex Miller (Clojure team)18:12:46

valid? works be evaluating the predicates

Alex Miller (Clojure team)18:12:40

The workflow seems exactly the same to me except the generator is already provided

kenny19:12:27

Right. But why is it written like that in clojure.core instead of providing actual specs for us to use?

kenny19:12:59

But there is no way to attach a generator to a pred without defining a spec

hiredman19:12:49

the generator is part of the identity of a spec

kenny19:12:10

But clojure.core is different. I’m wondering why it is different

hiredman19:12:13

new generator == new spec

Alex Miller (Clojure team)19:12:18

The core fns have built in mappings because that lets you build up specs in many cases without needing to write custom gens

Alex Miller (Clojure team)19:12:49

Like (s/and int? even?)

kenny19:12:08

So then why is there no way for us to extend that built in mapping so we can so similar things with our own predicates?

Alex Miller (Clojure team)19:12:13

I was not part of that decision process so I can’t say what exactly went into it. but certainly you can create specs by using s/spec or s/with-gen to add custom gens

Alex Miller (Clojure team)19:12:01

my suspicion is that it opens a bunch of complexities with respect to libs providing their own competing generators

Alex Miller (Clojure team)19:12:22

and maybe that was just a door Rich and Stu didn’t want to open

kenny19:12:16

Yeah I’m just not sure. It’d be great to know if that was actually what they decided. It seems useful but I am also concerned that it could lead to complexities.

kenny19:12:50

Technically you could have Spec extend IFn so you can define your predicate on the spec. But that feels a little hacky

danboykis20:12:22

how do I spec a map whose keys are strings i.e. {"foo" "bar"}?

Alex Miller (Clojure team)20:12:40

(s/map-of string? string?)

danboykis20:12:04

alexmiller: I wanted to make sure "foo" is present

danboykis20:12:24

do I build on top of contains?

Alex Miller (Clojure team)20:12:47

#(contains? % “foo”) ?

Alex Miller (Clojure team)20:12:14

I don’t know that I have enough info to actually answer your question :)

danboykis20:12:37

I thought there was maybe some way to instrument it on top of s/keys

danboykis20:12:03

contains? works

hiredman20:12:48

the problem with describing things entirely in terms of predicates is you lose the structure

danboykis20:12:35

hiredman, yep, I was thinking if it becomes to bothersome to do contains? everywhere to convert the string keys to keywords instead

danboykis20:12:10

i'm not sure how to best handle a case with a large map that has a bunch of strings for keys

Alex Miller (Clojure team)20:12:46

one hack is to (s/and (s/conformer clojure.walk/keywordize-keys) (s/keys :req-un [::a]))

Alex Miller (Clojure team)20:12:00

(didn’t try this, so could be typos there but you get the idea)

danboykis20:12:45

alexmiller thanks, I'll play with it

Alex Miller (Clojure team)20:12:54

@hiredman fyi, we consider the protocols internal right now and there is a reasonable chance they will still change

hiredman20:12:06

still alpha, etc, etc

Alex Miller (Clojure team)20:12:18

protocol might even go away

hiredman20:12:29

I was disappointed without much work that took to do 🙂

Alex Miller (Clojure team)20:12:13

the intention is that most people shouldn’t do it :)

hiredman20:12:11

I don't want to do it, but I also want to use spec as a data regex (including maps) without having to fiddle with the global registry

donaldball20:12:31

Do you mean you want the ability to spec a map with unnamespaced keywords without registering corresponding namespaced keywords in the spec registry?

hiredman20:12:17

maps without keyword keys exist too

hiredman20:12:11

you can easily say, with predicates, #(and (map? %) (contains? % "foo") (uuid? (get %) "foo")), but then you run in to issues if you want to spec the data

hiredman21:12:11

you can replace uuid? there with a call to valid? and some spec, but to conform and explain on the original spec it is a black box

hiredman21:12:02

one solution would be to replace predicates with pairs of predicates and navigators

eraserhd22:12:28

How could I compute a key for s/keys?

eraserhd22:12:00

(`is-valid` is effectively (is (s/valid? ...))

eraserhd22:12:05

dev=> (s/valid? (s/keys :req [::foo]) {})
false
dev=> (let [foo ::foo] (s/valid? (s/keys :req [foo]) {}))
true
dev=>

bfabry22:12:02

@eraserhd s/keys is a macro, so you need to compute the key at/prior to macroexpansion

bfabry22:12:46

boot.user=> (defmacro computed-foo []
       #_=>   (let [foo ::foo]
       #_=>     `(s/keys :req [~foo])))
#'boot.user/computed-foo
boot.user=> (s/valid? (computed-foo) {})
false

eraserhd22:12:40

Ah, eval works for this case (since it's a test and not production code)

bbloom23:12:42

am i doing :ret wrong? user=> (s/fdef f :args (s/cat) :ret string?) user/f user=> (defn f [] 1) #'user/f user=> (stest/instrument `f) [user/f] user=> (f 5) ExceptionInfo Call to #'user/f did not conform to spec: In: [0] val: (5) fails at: [:args] predicate: (cat), Extra input :clojure.spec/args (5) :clojure.spec/failure :instrument :clojure.spec.test/caller {:file "form-init7158439094984280972.clj", :line 1, :var-scope user/eval100801} clojure.core/ex-info (core.clj:4725) user=> (f) 1

gfredericks23:12:08

@bbloom instrument doesn't check :ret

bbloom23:12:20

.... what? why?

gfredericks23:12:43

something something instrument is for calls and generative testing is for rets

gfredericks23:12:08

it does or does not make perfect sense depending on who you ask; I think there's an explanation somewhere

gfredericks23:12:18

if this were irc we would just ask clojurebot for the link

bbloom23:12:35

count me in the camp that for whom that does not make any sense

gfredericks23:12:36

if this were irc we would just increment a counter in clojurebot to keep track of that

bbloom23:12:01

¯\(ツ)

gfredericks23:12:01

@bbloom my guess is the intention is when you test something, you instrument all the stuff it calls to make sure that stuff gets called correctly; checking rets is for when you're testing that function in particular, which you can do with clojure.spec.test/check, or by calling/asserting manually

gfredericks23:12:49

anyhow if you hate this you could add an alternative to instrument in schpec

bbloom23:12:35

ugh. i KINDA understand the thought process, but i don’t really buy it

bbloom23:12:59

i have a recursive function that has a small :ret violation deep inside it and i want to catch the error earlier than the root call from the unit test

bronsa23:12:19

s/assert in a postcondition ¯\(ツ)

bronsa23:12:04

(not saying I particularly like it)

bbloom23:12:03

yeaaaah, i’ll just use s/assert

bbloom23:12:38

i’m just annoyed that these specs weren’t getting run this whole time i thought they were