Fork me on GitHub
#clojure-spec
<
2016-12-20
>
gdeer8103:12:49

this spec (s/def ::string-num (s/and string? #(not= nil (re-find #"^[0-9]+$" %)))) is unsatisfiable to the generator. I wanted to make sure there wasn't a better way to spec out a string of numbers that could be satisfied before I wrote a custom generator

artemyarulin08:12:36

Hello. Is there a way to create connected spec generators? I have

(s/def ::user #{“user1” “user2”})
(s/def ::pwd #{“pwd1” “pwd2”})
(s/def ::acc (s/keys :req [::user ::pwd]))
I want generator that will returns me accounts like {::user “user1” ::pwd “pwd1”}, but with current setup it will return random user and pwd. I wonder - is there any way to somehow connect specs to generate valid accs for my context?

gfredericks12:12:45

@artemyarulin is this not what you're seeing, or else what do you mean by "valid accs"? https://www.refheap.com/124315

artemyarulin12:12:44

@gfredericks Well I meant that valid accounts are pairs of user1 pwd1 and user2 pwd2 and not user1 pwd2 with such data. It was solved like this:

(def credentials {"user1" "pwd1" "user2" "pwd2"})
(s/def ::acc (-> credentials seq set))
(s/def ::user (-> credentials keys set)})
(s/def ::pwd (-> credentials vals set)})

gfredericks12:12:19

oh; this seems like a strange way to make specs for user data

robert-stuttaford12:12:52

cool that it’s possible 🙂

Alex Miller (Clojure team)13:12:24

@artemyarulin: there is a screencast about "modeling" your data in creating generators

artemyarulin13:12:19

@alexmiller thanks, I’ll have a look

yogidevbear14:12:09

Hi everyone. I bought myself Living Clojure for a Christmas present (5 more sleeps to go!). Can anyone recommend a good "beginners" intro course to spec (as this isn't included in the book)? (Other than the standard docs)

yogidevbear14:12:31

I'm hoping to find something very beginner-friendly

yogidevbear14:12:02

Thanks in advance 😄

tjg14:12:24

@yogidevbear I haven’t watched this myself, but I like Lambda Island in general, and their intro to spec is a freebie: https://lambdaisland.com/episodes/clojure-spec

mpenet14:12:18

@artemyarulin seems like a s/and with the part containing the s/keys you're interested in with it's own gen might fit better imho

mpenet14:12:46

I had to do something like this for user profile data that had to return valid/realistic data (gender matching firstname, profile pic matching "fake" user etc)

mpenet15:12:59

in short, check out :gen in s/keys

mpenet15:12:18

./back to xmas stuff

joshjones16:12:29

@yogidevbear This is not a guide, but it’s been the most helpful for me to think of it this way — the question that helps get things going is: What am I trying to spec? So, I wrote this out in a table on my board, and it’s helped me immensely. First of all, remember that for all intents and purposes, every predicate is a spec. It answers yes or no when given something. If it’s an associative structure, like a map, you’ll use keys, map-of, and merge. If it’s “data”, you will use a set as a predicate (`#{}`), your own functions, built-in predicates like string?, pos-int?, and so on, or for ranges, int-of, double-of, and inst-of (for dates) If it’s a sequential structure (lists, vectors), you’ll use coll-of (where every element is validated by the same predicate), tuple (where each element may have a different predicate), or the regex operations which can be used to combine patterns of specs within the sequence: cat, *, +, ?, alt, keys*, &, and spec (for nesting structures) You will want to fdef some function specs as well. So, for me the hard part initially was taking this huge mass of functionality and finding “where do I start?” The above is a very rough, incomplete guide to what you should be using in the spec library to get started, based on what you specifically are trying to spec. Testing, instrumenting, generating data is another thing, but first learn the above and I think it will not be hard to transition from there.

mpenet17:12:48

to expand on the related key/val before, this ends up something like this:

(s/def ::user string?)
(s/def ::pwd string?)
(s/def ::acc (s/keys :req-un [::user ::pwd] :gen
                     #(gen/fmap first (gen/shuffle [{:user "user1" :pwd "pwd1"}
                                                    {:user "user2" :pwd "pwd2"}
                                                    {:user "user3" :pwd "pwd3"}]))))
at least the gen and spec parts are separated this way

mpenet17:12:15

you can do the same with gen/let or gen/one-of + 1 gen/return per pair

yogidevbear21:12:00

Thanks @tjg and @joshjones 👍 @joshjones I think I'm going to have to save your response somewhere so I can digest it further down the line once I'm a bit more familiar with Clojure and some of the terms you're using 😉

jiangts22:12:03

i’m a bit of a newcomer to spec, but when defining specs can we use other specs instead of predicates? I’m testing in CLJS and it seems that it’s allowed, but then exercise on coll-of seems to break.

jiangts22:12:50

The following is throwing an error in ClojureScript 1.9.293

(s/def ::a nat-int?)
(s/def ::many-as (s/coll-of ::a :kind set?))
(s/exercise ::many-as)

jiangts22:12:02

ah, checking a bit closer it seems that specifying :kind set? does not work

gdeer8122:12:41

I spec'd out a function that takes a set and makes a power set, wanted to know if I'm doing this right https://www.refheap.com/124325

gdeer8122:12:52

the funny thing is when you write an :fn spec for a function, now you have two functions to debug.

bsima22:12:18

hm, spec can't capture side-effects, can it?

gdeer8122:12:35

like if you had a function that fired rockets into space and you wanted test.check to be able to fail a case where there weren't rockets in the air?

bsima22:12:35

yeah, like there's no way to capture rockets-fired = true in the spec definition

bsima22:12:05

which makes sense, b/c spec isn't trying to be a type system like Haskell's or anything.

gdeer8122:12:45

unless your function returned a map like {:num-rockets 5 :target "moon" :fired true} but then its actually your function that captured the side effect

bsima22:12:55

yeah good point

joshjones22:12:54

@bsima you cannot capture effects outside a function (AFAIK), but you can capture the return value and its relationship with the args in the :fn portion of the fdef of course

joshjones22:12:56

I suppose you could use a :postcondition in the definition of the function, or check some outside state inside the function, since you have access to the state anyway.

gdeer8123:12:15

@bsima I spec'd out a function that updates a global atom https://www.refheap.com/124326 doesn't smell right

joshjones23:12:17

Since state is involved, there's no (good) way to avoid race conditions when verifying that the updated value is correct. Code elsewhere could have modified the atom.

gdeer8123:12:18

also, the generator is pretty abusive and will throw large numbers at it until it breaks 😆

gdeer8123:12:45

@joshjones well the scary thing is that it has passed test.check over 3k times now. I guess I'd have to use pmap to see if I can ferret out race conditions

joshjones23:12:27

i am guessing check runs the function sequentially, so no danger of seeing race conditions in this case