Fork me on GitHub
#clojure-spec
<
2017-02-08
>
ag00:02:53

or somehow tell test.check.generator/map or hash-map that I want keys to be out of the spec?

bfabry00:02:34

you should be able to read the keys out using s/form, assuming it's a simple spec

bfabry00:02:14

cljs.user=> (require '[clojure.spec :as s])
nil
cljs.user=> (s/form (s/keys :req-un [::foo] :opt-un [::bar]))
(cljs.spec/keys :req-un [:cljs.user/foo] :opt-un [:cljs.user/bar])

bfabry00:02:36

ofc if it's a more complicated spec you'll need to walk the form finding the key specs

cljs.user=> (s/form (s/and (s/keys :req-un [::foo] :opt-un [::bar]) identity))
(cljs.spec/and (s/keys :req-un [:cljs.user/foo] :opt-un [:cljs.user/bar]) cljs.core/identity)

bfabry00:02:35

(being able to drop into a 1.9 repl in sub-seconds rules, thank you planck)

ag00:02:54

hmmm, interesting. thanks!

ag00:02:19

@bfabry I did not know about s/form, just played with it - it’s nice. thanks again!

qqq00:02:22

what is the standard way of memoizing spec checks? I have a recursive data structure (say a tree), and I don't want it to have to verify the property holds on subtrees every time -- I want to somehow 'cache' "this node passed spec xyz" somehow

qqq00:02:28

when memoizing, can we somehow do it with "weak references" so that instead of having gc leaks, the object is thrown away when it's no longer needed ?

qqq00:02:44

I suspect one nice thing about meta data over memoization is that with meta data, with the object is no longer needed, and the gc gcs it, it takes away the metadata too

qqq00:02:59

with memoization, I fear it'll prevent the gc from doing its work because the spec function will keep th eobject around

hiredman00:02:43

why are you running spec checks over and over on a large structure? like, just don't do that

qqq00:02:34

if I have input argument spec validation on, wouldn't it run the spec every time the function is called?

qqq00:02:00

now, if this structure is recursive (say: this is a tree, where the sum of every subtree is a multiple of 3), then it ends up checking the entire tree every time

hiredman00:02:18

my first concern with that question is it sounds like you are planning to turn on spec instrumentation outside of your test suite

qqq00:02:42

that is true; is it bad to have spec instrumentation turned on in production code? if it's a costant time hit, I would not mind

hiredman00:02:53

it is bad, spec is not designed for that

hiredman00:02:40

I should say

hiredman00:02:53

the instrument stuff in spec is not designed for that

hiredman00:02:20

the intended use, as far as I understand, is to turn instrumentation on when running your test suite

hiredman00:02:26

and that is it

qqq00:02:40

I think assertions are good in dev code (not just in test quite). I'm trying to use spec as "ultra powerful assert" in dev code. You're saying this is bad, and I should not use spec as an assert ?

qqq00:02:39

For some functions, I want to assert that their input satisfies certain pre-conditions. These pre-conditions can be expensive (checking all subtrees); therefore, I'd like a way to cache this. Now, given that I want to do the above, can I do this with spec, or would spec be a bad match? I'm hoping that spec can do this, as I really like spec's language.

bfabry00:02:39

imo many many people will use spec the way you describe, but there are some things to be wary of: it will be slow. if you spec any functions that you pass as arguments those functions will be called multiple times to check that they pass the spec as part of instrumentation. instrumentation does not check return values

qqq00:02:28

@bfabry: we can assume I don't need to "run specs on functions passed as inputs" -- and my only conern at the moment being simple data structures and recurisve data sturcutres // functions are way too hard

hiredman00:02:23

if you don't have any automatic checking turned on, checking will only happen when you ask for it, so just don't ask for expensive checks more than once

bfabry00:02:59

fwiw there's no reason you can't use spec's data description language to validate data without using instrumentation. just call s/valid or s/conform directly. you could even wrap your calls to s/valid and s/conform in something that memoizes based on the object's reference id or whatever if you really want to tweak performance of that validation

qqq00:02:30

(defn my-valid [spec data] (or (contains? (meta data) spec) (s/valid spec data))

qqq00:02:34

or something of that nature, this is clever

bfabry00:02:00

yeah exactly, then you can stick that in :pre if that's your preferred mechanism

joshjones00:02:41

if the function is in clojure.spec.test, don't use in prod

bfabry00:02:49

you could even still associate the spec with the function, so you get the documentation, generation stubbing when wanted etc, just don't call s/instrument

qqq00:02:23

http://blog.fogus.me/2009/12/21/clojures-pre-and-post/ <-- this existed 7 years ago, finally starting to use it now

joshjones00:02:34

the spec guide under "Using Spec for Validation" gives the :pre and :post example for reference

qqq00:02:06

@joshjones: found it, thanks!

onetom04:02:11

Is it expected behaviour to get a clojure.spec/unknown explanation if a non-conforming spec has a custom generator, like this:

(s/def ::a (s/with-gen string? identity))
  (s/def ::some-map (s/keys :req [::a]))
  (s/explain ::some-map {::a nil})

In: [:app.deals/a] val: nil fails spec: :app.deals/a at: [:app.deals/a] predicate: :clojure.spec/unknown

onetom04:02:31

Insead of

(s/def ::a string?)
...
In: [:app.deals/a] val: nil fails spec: :app.deals/a at: [:app.deals/a] predicate: string?

seancorfield04:02:02

What sort of generator is identity?

seancorfield04:02:32

(hmm, and doesn't with-gen take a nilary function that returns a generator?)

seancorfield04:02:43

Yup, "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" -- identity does not satisfy that.

seancorfield04:02:29

So (s/with-gen anything identity) doesn't make sense... you're not going to get a valid generator from that?

seancorfield04:02:03

Yeah, if you (s/exercise ::a) you'll get

boot.user=> (s/exercise ::a)

clojure.lang.ArityException: Wrong number of args (0) passed to: core/identity
which is what I'd expect.

seancorfield04:02:31

So the error from s/explain isn't surprising but it is perhaps a bit misleading @onetom

onetom04:02:38

ah, sorry, i just threw identity in there because it's an irrelevant detail

onetom04:02:27

i observed the very same behaviour in our actual app with this real generator:

(s/def :deal/name (-> string?
                      (s/with-gen #(gen/fmap
                                     (fn [s] (str "<DEAL-NAME-" s ">"))
                                     (gen/string-alphanumeric)))))

onetom04:02:13

(s/def ::a (s/with-gen string? gen/string-alphanumeric))
(s/def ::some-map (s/keys :req [::a]))
(-> ::some-map s/gen gen/generate)
(s/explain ::some-map {::a nil})

=> #:boot.user{:a "lybK4CU4teXKCnErk9h5ajdGBu"}
In: [:boot.user/a] val: nil fails spec: :boot.user/a at: [:boot.user/a] predicate: :clojure.spec/unknown

seancorfield04:02:21

OK, that does s/exercise...

seancorfield04:02:18

Yeah, that sounds like it's worth a JIRA issue...

seancorfield04:02:27

I'd expect a better message, at least.

onetom04:02:09

and this is the very first time i wrote a custom generator for an actual real-world use case... thats my generic experience with software... sometimes im wondering im just unlucky. what comforts me slightly is that i have a friend who is an order of magnitude "unluckier" 🙂

onetom04:02:38

ok, i will make a JIRA issue. (this will be my first JIRA issue... im already worried what will happen ;)

seancorfield05:02:05

Functionally-linked people are very useful in QA'ing software 🙂

onetom05:02:22

I've created http://dev.clojure.org/jira/browse/CLJ-2107 but it seems I can't edit it to correct the markdown syntax in it 😕

seancorfield05:02:38

How's that? (edited)

seancorfield05:02:09

(uses {code} around code not three backticks)

seancorfield05:02:39

and {{ }} instead of backticks for inline code.

hiredman07:02:14

with-gen is a function, so it's arguments are evaluated, so the string? argument to with-gen is a function object, when spec is trying to find the name to report it does some stuff, which for symbols and keywords reports a good name, but for other Objects (including function objects) you get :clojure.spec/unknown

hiredman07:02:03

user=> (s/def ::a (s/with-gen (s/spec string?) identity))
:user/a
 (s/def ::some-map (s/keys :req [::a]))
:user/some-map
user=>   (s/explain ::some-map {::a nil})
In: [:user/a] val: nil fails spec: :user/a at: [:user/a] predicate: string?
nil
user=> 

hiredman07:02:40

wrapping with s/spec allows the spec macro to capture the meaningful, the symbol before evaluation

mpenet08:02:49

s/spec also takes :gen so you can avoid calling with-gen separately, I find it nicer personally

mpenet08:02:52

(s/def ::a (s/spec string? :gen identity))

mpenet08:02:37

I almost never use with-gen because of this now that I think of it

luxbock10:02:57

I have some data which I can neatly define a spec for using the regexp combinators of spec, which is great because I can generate example data for free, but for the functions I'm testing it's quite important that the returned sequences are vectors rather than list

luxbock10:02:22

coll-of and every allow you to define the type of the sequence, but they don't know about the structure of the sequence

luxbock10:02:03

is there any easier way to force the generators to produce vectors than using a custom generator that calls vec on them?

linuss10:02:45

Hey guys, I'm trying to use the simple-type generator in one of my specs, but I can't seem to get it to work. I currently have (s/def ::any (s/spec (fn[] (true)) :gen #(gen/simple-type))) but it seems that whatever permutation of the :gen value I use it returns an error

dergutemoritz11:02:11

@linuss Can you paste the error you get? At any rate, the parens around (true) look wrong, that means you are calling true as a function

linuss11:02:52

@dergutemoritz Ah, thanks! Yeah, that doesn't help. However, I'm still getting the following error: java.lang.IllegalArgumentException: No value supplied for key: (fn* [] (gen/simple-type))

linuss11:02:06

oh, hold up

linuss11:02:08

typo on my end

linuss11:02:53

java.util.concurrent.ExecutionException: clojure.lang.ArityException: Wrong number of args (1) passed to: specs/fn--10638 That's the error I keep getting

dergutemoritz12:02:44

@linuss Ah, right, your spec predicate function needs to accept a single argument.

dergutemoritz12:02:35

So (fn [x] true) would do the trick. You can use any? instead, too, which is the core function with the same behavior.

dergutemoritz12:02:13

Plus that clojure.spec has a default generator for it

linuss12:02:23

Oh wow, thanks!

dergutemoritz12:02:21

You're welcome!

linuss12:02:25

Oh, hm, I can't seem to find any?. Could you point me to the right location?

dergutemoritz12:02:41

@linuss It's in clojure.core since 1.9

triss12:02:04

hey all, I’m trying to run stest/check against some functions

triss12:02:12

but i get the following error:

triss12:02:51

...
{:result #error {
 :cause "Could not locate clojure/test/check/generators__init.class or clojure/test/check/generators.clj on classpath."
 :via
 [{:type java.io.FileNotFoundException
   :message "Could not locate clojure/test/check/generators__init.class or clojure/test/check/generators.clj on classpath."
   :at [clojure.lang.RT load "RT.java" 458]}]
 :trace ...

triss12:02:04

do i need a particular dependency?

not-raspberry12:02:44

Yes. [org.clojure/test.check "0.9.0"]

pbaille12:02:53

Hi, i'm curious about how/should spec can be used as dispatching system?

pbaille13:02:50

does this question even make sense? 🙂

dergutemoritz13:02:23

@pbaille Depends on what you mean by dispatching sytem 😄

pbaille13:02:39

something like multimethods

dergutemoritz14:02:39

Not really.. you can certainly use s/conform in a multimethod's dispatch function, though

dergutemoritz14:02:52

Which seems like it could be a useful thing

pbaille14:02:45

i've done a little gist about this, doesn't look really nice... https://gist.github.com/pbaille/b1bc0d05c2ec428e220fa28d28c8354f

pbaille14:02:53

looks like performance is an issue here, and the try catch stuff is ugly...

pbaille14:02:13

but that illustrate what i am trying to acheive

pbaille14:02:42

that's probably a terrible idea 🙂

linuss14:02:25

Would it be possible to write a spec for a function with side-effects, like slurp?

Tim14:02:12

maybe if it takes input?

cryptorat16:02:52

I am trying to write a definition for “weeks in a year” and having difficulty checking that the integer is < 53. (s/def ::valid-weeks-in-year (s/and ::non-negative-integer #(< % 53))) It is failing with

java.lang.ClassCastException: clojure.lang.MapEntry cannot be cast to java.lang.Number
. What am I missing?

cryptorat16:02:56

In a general case, how do I check that a number falls in a range?

linuss16:02:21

I think your error is somewhere else, this works without issue on my end

linuss16:02:01

(s/def ::non-negative-integer (s/and int? #(>= % 0))) (s/def ::valid-weeks-in-year (s/and ::non-negative-integer #(< % 53))) (s/valid? ::valid-weeks-in-year 10) => true

cryptorat16:02:09

oh, int-in. That looks like it will work.

cryptorat16:02:28

I wonder if my non-negative-integer is the problem. Let me try with yours.

cryptorat16:02:35

Yup, that is it.

cryptorat16:02:50

Well I learned two things. Thank you.

cryptorat16:02:49

The problem seems to lay in using `(s/def ::non-negative (s/or :positive pos? :zero zero?))` instead of #(>= % 0)

cryptorat16:02:01

And since or returns a map entry....

cryptorat16:02:14

Well that all makes sense now.

joshjones16:02:45

@cryptorat a non-negative integer is also known as a natural integer. 1.9 has a predicate for this, so you can just use nat-int? as your predicate, although for your case, as @ghadi said, int-in is probably more appropriate since you need an upper bound

cryptorat16:02:13

My understanding is that whether or not zero is included in the set of natural numbers can be debated. I figured best to avoid that in case someone changes their mind. Too cautious perhaps.

joshjones16:02:52

this is a good point — i doubt the definition will change in the clojure universe but good catch nonetheless. but as you have already seen, (s/int-in 0 53) is much better anyway for your case

akiel17:02:18

If you have two maps where map :a has :a/id and map :b likes to reference a particular map :a. How would you call the key in :b? Would you just use :a/id or would you create a new key called something like :b/a-ref?

joshjones17:02:28

@akiel an example of the two maps would help clarify

akiel17:02:31

either {:a/id 1 :b/name “foo”} references {:a/id 1 :a/name “bar”} or {:b/a-ref 1 :b/name “foo”} references {:a/id 1 :a/name “bar”}

joshjones17:02:43

what do you mean “references” ?

akiel17:02:12

a big disadvantage of using :a/id also for such kind of references is, that not every map containing an :a/id can be considered to be an :a.

akiel17:02:47

by references I mean the same what in relational databases forein keys do

akiel17:02:33

I can’t embed the referenced map directly, because that would blow up the data

akiel17:02:31

To make it more spec relevant - I ask because in spec keywords are used as names for something like types and that keywords are also used in maps to name something like attributes.

dergutemoritz17:02:52

@akiel I'd go with a different name, i.e. the :b/a-ref version. Because a reference to a thing is not the same as the thing itself after all.

dergutemoritz17:02:33

Then again, embedding the data directly shouldn't really have that much of an impact if you have the thing that is pointed to in memory anyhow

akiel17:02:25

@dergutemoritz I lean also towards using :b/a-ref. Regarding embedding directly you are right, it won’t cost memory. There I was wrong.

akiel17:02:27

@dergutemoritz But it would cost on wire. I need to transport that data over wire.

dergutemoritz17:02:32

Another reason to name it :b/something is that you can then use a name that describes the relationship. E.g. if :a is :person and :b is :book then you could have (s/def :book/author :person/id) which I'd say would even justify leaving off the -ref suffix.

akiel18:02:17

@dergutemoritz Yes you are right - role names.

akiel18:02:30

@dergutemoritz A related thing: Would you go for all keys in a person to start with the namespace :person or would you also use other common keys in a person? Like :person/name, :book/name vs. just :common/name.

dergutemoritz18:02:57

@akiel I don't know, I think that's subject to an ontological debate you have to have with your domain experts 🙂

dergutemoritz18:02:01

Note that you could have both in a way: (s/def :common/name string?) (s/def :person/name :common/name)

dergutemoritz18:02:49

That way you could attach additional meaning to :common/name and also have it influence :person/name

akiel18:02:36

@dergutemoritz The domain is given in my case. But I just think about map keys in relation to specs. With 20 different names, you end up with 20 specs for names which are all the same. I’m not sure if thats a bit of an antipattern now regarding to spec.

dergutemoritz18:02:06

It really depends on your domain

akiel18:02:50

@dergutemoritz Than you have specs like :common/name which are never used as keys in a map. That doesn’t have to be a problem - just thinking about it.

dergutemoritz18:02:25

Yeah, that would be an abstract spec probably

dergutemoritz18:02:14

I guess the question is whether the concept of a name is universal in your domain or specific to each entity. Or maybe a mixture of those.

dergutemoritz18:02:57

But I don't feel like I've fully figured this out, yet, either

akiel18:02:10

For example I have a transaction type which can have values like :insert or :update. It has the same meaning and the same values for each entity. Should I have a transaction type for every entity or just one?

dergutemoritz18:02:19

If it has the same meaning in all contexts, then I'd go with a single one

akiel18:02:45

@dergutemoritz Thanks, that sounds reasonable. Same name for the same thing. 🙂

dergutemoritz18:02:18

At least that's my current understanding of things 🙂 If anyone else has another interpretation, I'm all ears

akiel18:02:53

That's why I liked to discuss a bit on that matter.

seancorfield23:02:43

Was that meant for a different channel @zane ?

zane23:02:14

Sure was!