Fork me on GitHub
#clojure-spec
<
2016-08-30
>
ag00:08:30

oh no.. I don’t think I know how to solve it with s/keys… the problem where I need to have a spec for a map where values picked up from a vector and associated values are together

ag00:08:40

so this is how I am generating a map with test.check.generators:

(g/let [acnt (g/elements ledger-accounts)]
      (g/hash-map
        :id                    (uuid-gen)
        :account-name          (-> acnt :account-name g/return)
        :account-type          (-> acnt :account-type g/return)
…
how can I do the same thing with spec?

ag00:08:40

a map where related values pulled out of a vector?

bfabry00:08:08

@ag are you using this spec for validation as well as generation?

ag00:08:51

I am planning

ag00:08:59

I don’t have a spec yet 🙂

ag00:08:27

I am playing with clojure.spec, and I feel utterly stupid ;(

ag00:08:22

should I be separating spec and generation parts?

bfabry00:08:31

well it depends

bfabry00:08:59

but it seems unlikely that you'll know all of the values of something like :account-name before runtime

bfabry00:08:19

have you read the guide?

bfabry00:08:59

also, I reckon the clojure spec screencasts by stuart holloway here https://www.youtube.com/channel/UCaLlzGqiPE2QRj6sSOawJRg might help. particularly the last one on customising generators

ag00:08:52

that’s the thing, account-names are predefined. and types are predefined. I need to have a map with bunch of keys along with name and type where name comes from that vector and type is the associated type (from the same vector)

bfabry00:08:39

ok, honestly it's strange enough that there's probably not a way to express it and still get generators so you'll probably need to specify the generator like you did above, and do the spec portion separate

bfabry00:08:21

I'd probably write the spec as

(s/def ::account-name (set (map :account-name leger-accounts)))
(s/def ::account-type (set (map :account-type ledger-accounts)))
(s/def ::id uuid?)

(s/def ::your-map (s/and (s/keys :req-un [::account-name ::account-type ::id] :gen YOUR-GENERATOR) #((set (select-keys ledger-accounts :account-name :account-type)) (select-keys % :account-name :account-type))))

Alex Miller (Clojure team)00:08:13

@ag it sounds like you are expressing what have been called hybrid maps

Alex Miller (Clojure team)00:08:46

Which you can spec with a merge of keys and every of tuples of key-value pairs

Alex Miller (Clojure team)00:08:59

I've given some examples here in the past

ag00:08:40

Yeah I’m watching Stu’s screencast “customizing generators”, looking into gen/bind, gen/tuple etc.

ag01:08:50

so why for simple predicates generator won’t work:

(s/def ::more-than-five #(< 5 %))
(s/conform ::more-than-five 6)
(s/exercise ::more-than-five)

clojure.lang.ExceptionInfo: Unable to construct gen at: [] for: :user/more-than-five

gfredericks01:08:16

@ag that would be a hard thing to support in general

gfredericks01:08:13

if you do (s/and integer? #(< 5 %)) it will at least make an attempt

gfredericks01:08:33

because clojure.spec has a registry of generators for some of the built-in predicates

ag01:08:19

oh, ok… thanks!

ag01:08:18

once again, how do I create s/def with a custom generator? I think I saw it somewhere?

sattvik02:08:18

I beleive it is (s/def ::foo (s/with-gen spec gen))

ag07:08:24

erhm… how do I refer to a spec s/deffed in other namespace? I am trying to put spec in .cljc file, is that possible at all?

ag07:08:55

I think I found it… so if spec is in foo.clj then require [foo :as f] … (s/exercise ::f/my-spec

ag07:08:34

still can’t figure out how to refer to spec (and use it without having to prefix it with namespace)

emrehan10:08:17

thanks all; clojure.spec/merge is exactly what I was looking for. I even read the whole API but I was quite sleepy.

emrehan10:08:29

How can I contribure to clojure.spec? docs/dev/testing/benchmarking?

jannis12:08:16

Hi. Using the latest ClojureScript version, I'd like to get all checkable syms (with cljs.spec.test/checkable-syms, filter them and call clojure.spec.test/check on each of them individually). However, if I do something like (doseq [sym syms] (st/check sym)) it tells me sym is unresolvable. This works fine in Clojure but in ClojureScript, am I supposed to pass in something else than symbols?

spinningtopsofdoom15:08:19

I'm looking to make a spec that validates nested maps (e.g.` {:value {:foo :bar :key :value}}`).

spinningtopsofdoom15:08:44

So far I'm only able to get one level of nesting via spec/keys

sattvik15:08:43

You can use two different specs: one for the inner map and then use it in the outer map.

sattvik16:08:07

There are other options, but I think that’s one of the easiest.

sattvik16:08:55

(s/def ::value :req-un [::foo ::key])
(s/def ::outer :req-un [::value])

spinningtopsofdoom16:08:27

I also have the need to have different specs for value (e.g. http://clojure.org/guides/spec#_multi_spec) . For example {:value {:foo :bar}} and {:value {:key :value}}

sattvik16:08:05

Well, in that case, I think you could do with more specs:

sattvik16:08:51

(s/def ::foo-value (s/keys :req-un [::foo]))
(s/def ::key-value (s/keys :req-un [::key]))
(s/def ::value (s/or :foo-value ::foo-value :key-value ::key-value))
(s/def ::outer (s/keys :req-un [::value]))

sattvik16:08:30

However, not all of those specs are fully specified, e.g. ::user.

ag17:08:29

so how come this

(s/def ::my-map (s/cat :id string?))
(gen/generate (s/gen ::my-map))

ag17:08:42

generates: ("pn7zcHi1WDk830ZltM5L5Yl82E0Ul”)

ag17:08:49

and not a map {:id “somenonsense”} ?

djwhitt17:08:50

s/cat specifies a sequence. ":id" in that code is specifying a label rather than a map key

ag17:08:48

so what should I use instead, if I don’t want to use s/keys?

bfabry17:08:51

you should use s/keys

djwhitt17:08:02

hmm... I'm not sure (just learning spec myself). any reason you can't use s/keys?

ag17:08:14

because it doesn’t allow “inline specs” e.g. s/keys (s/def :id string?)

djwhitt17:08:57

I think spec is intentionally trying to steer people away from inline specs like that

djwhitt17:08:36

you could make a separate spec for the key and use req-un to allow unnamespaced keys if that's what you're looking for

bfabry17:08:51

@ag giving every spec a fully qualified name is one of the core design goals of spec, things are going to get difficult if you try not to follow it

ag17:08:26

guys, I need a good spec for values of milliseconds after the Unix epoch. i.e.:`clj-time.coerce/to-long`

ag17:08:53

can you help?

bfabry17:08:39

@ag (int-in? 0 Long/MAX_VALUE)?

ag17:08:15

mmm that’s too simple, I need to make sure values are of reasonable datetime range, something like from 1900 to 2100 maybe

bfabry17:08:25

well unix epochs don't go down to 1900

bfabry17:08:27

so that's out

bfabry17:08:59

(int-in? 0 (c/to-long #inst "2100-01-01"))

Alex Miller (Clojure team)18:08:18

should really use int-in if you’re defining a spec (not just the predicate int-in?), as then you will get generator support

Alex Miller (Clojure team)18:08:38

and there is a new fn for extracting the ms - inst-ms in core

Alex Miller (Clojure team)18:08:58

so I would say (s/int-in 0 (inst-ms #inst "2100-01-01"))

Alex Miller (Clojure team)18:08:25

assuming you’re cool with the year 2100 problem

Alex Miller (Clojure team)18:08:44

inst-ms calls into the Inst protocol, which is extended to both Date and (if you use JDK 8), Instant. Could also be extended to a JodaTime Instant.

ag18:08:15

I don’t understand why this is failing:

(s/def ::account-name string?)
(s/def ::account-type keyword?)
(s/def ::description string?)

(s/valid? (s/keys :req [::account-name ::description ::account-type])
  {:account-name "pre-fund" :account-type :internal :description  "Pre-Fund”})

sattvik18:08:07

:req requires namespace-qualified keys.

sattvik18:08:16

You may want to use :req-un instead.

Alex Miller (Clojure team)18:08:26

:: will autoresolve and fully qualify

Alex Miller (Clojure team)18:08:33

oh you mean in the data

ag18:08:14

oh, ok… thanks a lot!

ag18:08:59

Sorry for bugging you with bunch of noob questions. Trying to solve real problem with spec. If I don’t get this right sooner, would be asked to stop my experiments.

Alex Miller (Clojure team)18:08:14

noob questions are good

Alex Miller (Clojure team)18:08:41

I asked Rich a lot of noob questions at the beginning too :)

ag18:08:36

is there a way to pass original value (I dunno of spec) to custom-generator function, when it’s created with s/with-gen?

ag18:08:02

something like (gen/generate (s/gen (s/with-gen ::account-balance-updated #(gen-account-balance-updated %))))

ag18:08:06

I guess I can refer to ::account-balance-updated from inside the gen-account-balance-updated, yet thinking if there’s more “generic” way

ag18:08:20

eh… I guess I can just pass it as is

ag18:08:23

nevermind

sattvik18:08:58

Well, you can do something like use gen/fmap or gen/bind.

sattvik18:08:16

Though, I am not sure that really answers your question…

Alex Miller (Clojure team)18:08:02

but you can define both the spec and the spec-with-custom-gen to do so

ag18:08:09

so I have a spec for a map structure, I need to use custom generator function where I would generate all the fields of that map, except of few selected, those should come from a predefined variable

Alex Miller (Clojure team)18:08:59

it’s easiest to use gen/fmap and source it with (s/gen (s/keys ::a ::b)), then in the function just merge with the constant map

Alex Miller (Clojure team)18:08:09

where ::a and ::b are the variable parts

Alex Miller (Clojure team)18:08:44

@ag

(s/def ::a int?)
(s/def ::b int?)
(s/def ::c string?)
(s/def ::m (s/keys :req [::a ::b ::c]))
(s/def ::m (s/with-gen (s/keys :req [::a ::b ::c]) (fn [] (gen/fmap #(merge {::c "xyz"} %) (s/gen (s/keys :req [::a ::b]))))))
(gen/sample (s/gen ::m))
;; (#:user{:c "xyz", :a -1, :b 0} #:user{:c "xyz", :a 0, :b -1} #:user{:c "xyz", :a -1, :b 0} #:user{:c "xyz", :a -1, :b -1} #:user{:c "xyz", :a 0, :b 0} #:user{:c "xyz", :a -10, :b 1} #:user{:c "xyz", :a 3, :b -2} #:user{:c "xyz", :a -1, :b 40} #:user{:c "xyz", :a 45, :b -2} #:user{:c "xyz", :a -2, :b -7})

Alex Miller (Clojure team)18:08:08

@eraserhd what’s the point of that gen/tuple?

sattvik18:08:11

Could it be that the tuple is adding an extra layer of being a collection, i.e. instead of returning [task] it is returning [[task]]?

Alex Miller (Clojure team)18:08:37

seems like you just need vector in that case

Alex Miller (Clojure team)18:08:17

or you might just want to fmap with vector inside the fn

eraserhd18:08:50

@alexmiller Er, we found out that spec conforms the generator's results to the spec.

eraserhd18:08:12

We had something with multiple values in the tuple, but deleted code until we found out where there was a problem.

eraserhd18:08:21

(except that we didn't know that part)

Alex Miller (Clojure team)19:08:52

yes, spec does not trust the generator

Alex Miller (Clojure team)19:08:22

even custom gens must produce values valid for the spec

sattvik19:08:05

Hmmm… so what’s the best way to create fixtures (for lack of a better term) with spec? This could range anywhere from setting up an environment within which to run the tests to testing that the output from the function under test matches a value generated from a replaced/stubbed function. check seems really useful for relatively pure functions, but having to orchestrate a lot via instrument seems clunky. Does anyone have any recommendations?

Alex Miller (Clojure team)19:08:06

if you want fixtures and assertions, use a testing framework which has them ?

Alex Miller (Clojure team)19:08:25

that is, wrap a clojure.test around instrument+check

Alex Miller (Clojure team)19:08:47

instrument could go in a fixture too if it applies broadly

sattvik19:08:33

Yes, I’ve done a bit of that. I have gotten it to work, but I was a bit unsatisfied with the result. I felt that the end result was a bit clunky and harder to understand/maintain compared to just using a traditional deftest.

sattvik19:08:31

I will continue to experiment with it, but I was curious to see if anyone else had any experience with non-trivial testing using spec.

sattvik19:08:45

Well, I am still a bit of a spec beginner (have been using it for less than a week), so there are things I may be missing. Some things that could help include: 1. More documentation around instrument, especially with examples of using the various options. 2. It would be nice to be able to call instrument with just options rather than (instrument (instrumentable-syms) opts). 3. The lack of a with-instrumentation macro makes setting up ‘fixtures’ harder. It was a simple one to write, but it would be nice to have it baked in. 4. Overall, specifying things through instrument doesn’t read as well. When this sort of thing is written out long-hand in code, it is easier to understand how the flow works. Instrumentation is sort of like defining callbacks before you need to use them, which removes some of the context from them. 4. The last one it is a little bit harder to describe. Instrumenting is very good about modifying things that will happen during the execution of the function, but sometimes I want to do something around the execution of the function. I can do that by specifying a test function and running spec on that, but it would be nice if there was a more direct way of doing that with spec.

Alex Miller (Clojure team)19:08:50

1. for sure :) more will be coming eventually. 2. did you find clojure.spec.test/enumerate-namespace? it helps with building sym lists. 3. there is a ticket for that that I was just looking at. there are questions - feel free to weigh in. http://dev.clojure.org/jira/browse/CLJ-2015 4. I can see what you mean, prob worth seeing more examples 4. that’s interesting. sounds like a check fixture? I assume you meant “running check”, not “running spec” above

gfredericks19:08:34

woah CLJ tickets have exactly caught up with the calendar year

Alex Miller (Clojure team)20:08:26

now if we can just slow down to 1 per year, that will always be true

gfredericks20:08:10

sounds doable

sattvik20:08:14

Regarding (4), you are right. I set up a method to handle (is (checks? foo/bar {…}))` where the last two arguments are just used to invoke stest/check. I first started by putting my fixture in a separately defined test function that invoked the the function to test. It works, but adds a bit of noise (also there is not a way to automatically inherit the function spec of the original). In a case where the fixture is transparent, i.e. takes the same args/returns the same result, I was able to write a macro that too the var name’s symbol and a higher-order function that served as the test fixture. That macro dereferenced the var giving the old value a local binding, and with-redefed the var to invoke the fixture (which took the old binding as an argument and returned a new function that behaved like the old one plus the fixture logic).

sattvik20:08:42

I had to do with-redef because I couldn’t :replace the function under test.

sattvik20:08:17

That technique works when the fixture is transparent, but perhaps I want to create a fixture that has a different signature than the function I am testing. For example, I might be testing that a function will eventually invoke a stubbed function that will return generated data. I want to check that the return value of the function under test satisfies a predicate that is partly dependent on the data that comes from the stubbed function, not just the inputs.

sattvik20:08:17

This is all doable right now by creating such a function and properly specifying the test function and instrumenting the third-party function.

sattvik20:08:52

This is all doable right now by creating such a function and properly specifying the test function and instrumenting the stubbed function.

sattvik20:08:48

I suppose it might make more sense with an example…

ag22:08:14

how can I create generate based on predefined vector? let’s say I have a vector [{:name “Anna”}{:name “David}] ..etc, I need a generator that creates vector with the same amount of elements, with added fields let’s say :age and :height?

ag22:08:33

I guess I’ll just wrap things into gen/return and for

gfredericks22:08:45

@ag so you want a generator that completes the maps?

gfredericks22:08:34

You could use gen/vector with a fixed length and gen/hash-map

ag22:08:39

I need to create a bunch of accounts with some predefined fields and some randomly generated fields, and need to create bunch of other structures associated

gfredericks22:08:27

(gen/fmap #(map merge % predefined) (gen/vector (gen/hash-map ...) (count predefined))) @ag would something like ← that work?

ag22:08:30

lemme try… thanks!