Fork me on GitHub
#clojure-spec
<
2016-07-22
>
lvh02:07:55

What’s the preferred way to check if a coll has only unique elements?

lvh02:07:05

Just a pred? I’d like the generators to be efficient.

Chris O’Donnell02:07:50

@lvh: I think coll-of and every use clojure.test.check.generators/vector-distinct under the hood

lvh02:07:29

ah; that solves part of the problem

Chris O’Donnell02:07:57

rather, they use vector-distinct if they get :distinct true

lvh02:07:20

ah! I missed that opt

lvh02:07:49

does coll-of take the same opts as every?

Chris O’Donnell02:07:20

this is the entire body of the coll-of macro: backtick before ( ---> (every ~pred ::conform-all true ~@opts)

Chris O’Donnell02:07:02

damn, how do you get a backtick in a code block -_-

lvh03:07:45

ah, awesome

lvh03:07:54

I should really just read the source of that ns

lvh03:07:30

I ran some test.check specs, but even with only 100 samples I’m getting actual: java.lang.OutOfMemoryError: GC overhead limit exceeded. I guess recursive data structures can grow big.

lvh03:07:35

Is there something extra weird that goes on when you test against (for-all […] true)? I was expecting that to trivially pass.

lvh03:07:54

(that seems like it would be the normal behavior once your code works.)

glv03:07:52

@lvh is a generator in control of the size (depth or breadth) of the generated structure? The current integer generators in spec grow very quickly. By the 20th test, you're probably in 8-digit range.

madstap04:07:54

Is there a way to say that if there are any numbers, the string also needs to be there, but if there aren't, it's optional? (s/def ::xs (s/cat :str (s/? string?) :nums (s/* number?) :key keyword?))

madstap04:07:10

;; so this should be valid
  (s/valid? ::xs ["s" 2 3 4 :k])

  ;; And this
  (s/valid? ::xs ["s" :k])

  ;; but not this
  (s/valid? ::xs [2 3 4 :k])

bfabry05:07:20

@madstap: (s/or :regex1 (s/cat ...) :refex2 (s/cat ...)) seems simplest to me

Alex Miller (Clojure team)05:07:21

(s/def ::xs
  (s/cat :pre (s/alt :opt1 (s/cat :str string? :nums (s/* number?))
                     :opt2 (s/? string?))
         :key keyword?))

mpenet07:07:03

What do you think makes more sense for a lib with optional specs: ship with specs in a separate namespace or have specs in a separate repo even. former requires macro hackery to allow to run/use with clj1.9- latter is just a dependency.

mpenet13:07:07

any idea why this fails when trying to validate with it:

(s/def :foo  (s/fspec :args (s/cat :err #(instance? ExceptionInfo %))
           :ret any?))

mpenet13:07:35

(s/valid? ::foo (fn [x] 1)) -> ExceptionInfo Unable to construct gen at: [:err] for: (instance? clojure.lang.ExceptionInfo %)  clojure.core/ex-info (core.clj:4724)

mpenet13:07:40

oh I see on the guide

Chris O’Donnell13:07:34

@mpenet: regarding your earlier question, can you not use the port someone made of spec to clojure 1.8?

mpenet13:07:59

it was more of a general question when publishing oss

Chris O’Donnell13:07:44

oops, I missed your "specs in a separate namespace" option

mpenet13:07:09

about my recent issue, I wonder why gen is coupled like this to preds

mpenet13:07:12

I am mostly interested in instrumentation in that case, yet I have to specify a generator apparently

Chris O’Donnell13:07:10

I would guess that valid? checks validity by generating some arguments using the :args generator and then checking that the return values match your :ret and :fn predicates

mpenet13:07:27

apparently

mpenet13:07:08

so instrumentation works without having to specify more here. good

Chris O’Donnell13:07:13

that's good to know

mpenet13:07:33

well actually no it doesn't

Chris O’Donnell13:07:58

it worked for me here:

=> (defn foo [& exs] (map str exs))
=> (s/fdef foo :args (s/cat :err #(instance? clojure.lang.ExceptionInfo %)) :ret any?)
=> (stest/instrument `foo)
=> (foo 1 2 3)
ExceptionInfo Call to #'user/foo did not conform to spec:
In: [0] val: 1 fails at: [:args :err] predicate: (instance? clojure.lang.ExceptionInfo %)
:clojure.spec/args  (1 2 3)
:clojure.spec/failure  :instrument
:clojure.spec.test/caller  {:file "form-init8739029786060363099.clj", :line 990, :var-scope user/eval90084}
  clojure.core/ex-info (core.clj:4724)
=> (foo (ex-info "test" {}))
("clojure.lang.ExceptionInfo: test {}")

Chris O’Donnell13:07:16

how did it fail for you?

mpenet13:07:44

not the same in my case, it's a function that takes another fn as argument, the fn passed fails to gen

mpenet13:07:20

so no s/fdef but s/fspec

mpenet13:07:58

(s/def ::alia.execute-async/error
  (s/fspec :args (s/cat :err (instance-pred clojure.lang.ExceptionInfo))
           :ret any?))

mpenet13:07:11

(s/valid? ::alia.execute-async/error (fn [x] :meh))

Chris O’Donnell13:07:38

it tries to call valid? on the function returned rather than doing something like propogate the instrumentation to the returned function

mpenet13:07:13

same when it's on the test suite via instrumentation of the parent fn

mpenet13:07:48

I understand it's necessary when running valid? on the single spec, but via instrumentation not really

mpenet13:07:45

it probably is slow too

mpenet13:07:52

@alexmiller: any thoughts on this?

mpenet13:07:34

In my mind instrumentation should "only" be pre/post assertions based on specs. no gen involved (same as plumatic/schema actually)

lvh14:07:17

@glv: Yes, I suppose so.

lvh14:07:26

at line 36, you’ll see ::properties is a map-of keywords to ::schemas

lvh14:07:29

(so recursive there)

lvh14:07:47

it’ll be worse when I add arrays, because those are also recursive json schemas

glv14:07:28

Oh … no, it doesn’t look like a generated value is in controlling the depth … in fact, nothing is controlling it except chance.

glv14:07:57

So on line 36, there’s a 1-in-7 chance that the generator will recur and add another level to the structure … but at line 32, there’s a 50% chance. It’s easy to imagine the process rolling the dice just right so that the structure grows until it fills the JVM’s memory allocation, and that seems to be happening.

glv14:07:07

The problem for writing the specs is this: you’ve accurately captured the semantics for the purposes of verification, but when generating for tests, you want to add some additional constraints. I ran into this when testing a 2D grid … in production code I don’t want to arbitrarily restrict the allowable sizes, but in my specs, I was ending up with grids that had many millions of cells, which was slow, failure-prone (because I also got out-of-memory errors) and also pointless (because a bunch of tests with 25x25 grids will catch any problems — the size of the grid isn’t really the important factor).

glv14:07:46

So I ended up with this spec:

;; restrict grid sizes for testing, but allow larger grids in production.
(s/def ::grid-dimen     (s/with-gen (s/and integer? #(>= % 2))
                                    #(s/gen (s/int-in 2 25))))

glv14:07:20

It doesn’t enforce an upper bound for validation, but it does for generation.

glv14:07:51

You might need something similar, but rather than limiting a size (because you don’t have one) you can alter the probabilities, so that the generator for ::additional-properties only chooses to recur 10% of the time, say, instead of 50%.

Alex Miller (Clojure team)14:07:51

when running check etc you can supply generator overrides

Alex Miller (Clojure team)14:07:45

same for exercise, gen, and instrument

Alex Miller (Clojure team)14:07:06

this allows you to override the generator with a more specific one at test time

Alex Miller (Clojure team)14:07:24

also see s/*recursion-limit*

mpenet14:07:34

I saw this. But shouldn't it be possible to avoid generators usage for instrumentation?

mpenet14:07:29

since it's all predicates, just wrap args/ret

Alex Miller (Clojure team)15:07:25

you can do so with the replace/stub functionality in instrument

Alex Miller (Clojure team)15:07:57

or maybe I’m not understanding your question

mpenet15:07:16

I don't know if you read my issue earlier

mpenet15:07:59

I imagined that instrumentation would work without having the need to stub anything since it's all specified, I didn't expect it to run "gen" on arguments (hundreds of calls)

Alex Miller (Clojure team)15:07:11

there is still some unfinished work in this area

mpenet15:07:34

oki, glad to hear that, so in the final design "gen" wouldn't be required by instrumentation code

mpenet15:07:06

ah indeed, I tried to look at jiras, I missed that one I guess

glv15:07:52

@lvh: just to test my hypothesis, try changing ::additional-properties to this and see if that helps:

(s/def ::additional-properties
  (s/with-gen
    (s/or
      :implicit-additional-properties boolean?
      :explicit-additional-properties ::schema)
    #(sg/fmap (fn [d10]
                (sg/generate (s/gen (if (= d10 1)
                                        ::schema
                                        boolean?))))
              (sg/choose 1 10))))

glv15:07:00

You’ll get a nested schema as the generated value only 10% of the time.

gfredericks15:07:40

alexmiller: ugh that "open intervals" email makes me wish I'd made the bounds opts on those numeric generators named #{:< :<= :> :>=} :/

glv15:07:49

(sg is aliased to clojure.spec.gen)

gfredericks15:07:22

alexmiller: I'd consider deprecating the old opts and adding those four if you think that'd be useful for spec

Alex Miller (Clojure team)15:07:31

I don’t think anything needs to change with it, it’s fine

gfredericks15:07:38

independently I also halfway regret how :NaN? and :infinite? interact with :min and :max

gfredericks15:07:06

or don't interact rather

gfredericks15:07:41

but that's harder to change without technically breaking

rickmoynihan16:07:53

Hmmm... any tips for debugging stackoverflow exceptions in specs?

rickmoynihan16:07:32

also is it possible to reset the spec registry?

rickmoynihan16:07:47

pretty sure I've heard people ask about this before

mpenet16:07:12

Maybe (reset! #'clojure.spec/registry-ref (atom {}))

mpenet16:07:26

(Untested)

rickmoynihan16:07:07

I've probably got a mistake in my specs...

rickmoynihan18:07:09

I had a typo along the lines of by mistake (s/def foo ::foo)

rickmoynihan18:07:24

Is it possible to call an s/fdef'd function and validate its :ret spec?

rickmoynihan18:07:45

I saw the guide said it wasn't supported because that's for testing

Alex Miller (Clojure team)18:07:08

the only place that’s checked by spec is during check

Alex Miller (Clojure team)18:07:18

you can of course obtain and check it yourself

Alex Miller (Clojure team)18:07:00

something like (s/valid? (:ret (s/get-spec 'user/foo)) ret)

rickmoynihan18:07:03

nice - I'd missed that one

xcthulhu18:07:21

@alexmiller: As I opined on reddit, I don't see any deep reason for why you can't just have in clojure.spec a schema transpiler:

(spec/def ::json-api
     (spec/schema
          {:id       uuid?
           :text     str?
           :rating   (int-in 0 5)}))
This would make migrating a lot less annoying.

Alex Miller (Clojure team)18:07:12

Clojure is not going to provide a schema transpiler

Alex Miller (Clojure team)18:07:37

b/c we have other things to do

Alex Miller (Clojure team)18:07:46

people are more than welcome to make one

Alex Miller (Clojure team)18:07:28

would we also create a truss transpiler?

Alex Miller (Clojure team)18:07:32

and a herbert transpiler?

Alex Miller (Clojure team)18:07:15

things in core are forever

xcthulhu18:07:51

I suppose. IDK, clojure.spec.gen/fmap is in there which is awkward.

xcthulhu18:07:12

I don't really grok the logic behind what gets in and what doesn't

Alex Miller (Clojure team)18:07:35

gen is just a dynamically loaded skin around test.check

xcthulhu18:07:03

Well, so there's tiny little monad in a framework where everyone did a great job of dodging that sort of thing.

Alex Miller (Clojure team)18:07:33

you can ignore it’s monadic nature if you like

xcthulhu18:07:51

All I'm saying is that great pains were made to wrap test.check, but the lesser pain of wrapping schema type syntax was ignored for some reason.

xcthulhu19:07:19

It's your product, everyone picks and chooses which wheels they want to reinvent I suppose.

Alex Miller (Clojure team)19:07:52

test.check is a Clojure contrib library, following the same contributor agreement, license, and dev methodology as Clojure itself (to make things like this possible)

Alex Miller (Clojure team)19:07:41

Schema is not (and I mean nothing negative towards Schema in this regard, they are just much different from a core perspective)

xcthulhu19:07:51

Okay, so I guess we just need a clojure.contrib.spec.utils library.

Alex Miller (Clojure team)19:07:54

spec was designed to solve a set of problems, not to replace Schema (even though it solves an overlapping set of problems)

xcthulhu19:07:10

With schema and a dynamically loaded test.check/let

xcthulhu19:07:21

Since that would be super nice

Alex Miller (Clojure team)19:07:29

let is tricky as it’s actually a macro

Alex Miller (Clojure team)19:07:47

Stu did the work on gen, but I suspect that’s the only reason it’s not there

Alex Miller (Clojure team)19:07:55

The Clojure contrib libs are primarily standalone libs without external deps (there are exceptions, but that’s the ideal). There is not going to be a contrib lib related to schema. But there is nothing stopping someone from creating a lib to do what you suggest.

Alex Miller (Clojure team)19:07:58

if we think something is fit for spec, it will go into spec, not into a lib

xcthulhu19:07:49

I'll go try and write the little let macro for you

xcthulhu19:07:27

But let is just like the do notation in Haskell, wrapping bind

xcthulhu19:07:46

I'll do it for you this weekend.

Alex Miller (Clojure team)19:07:06

I think let would basically have to be copied into gen rather than dynamically loaded

xcthulhu19:07:50

So I'll write it and post it here for you.

Alex Miller (Clojure team)19:07:53

you can file a jira if you like, but no guarantees for anything

xcthulhu19:07:25

Nah, I'll just post it here in a gist or whatever unless you need me to sign something

xcthulhu19:07:46

It's like 15 lines tops

Alex Miller (Clojure team)19:07:54

we don’t take contributions via slack :)

Alex Miller (Clojure team)19:07:21

sign the CA, file a jira, supply a patch

xcthulhu19:07:02

Oh man red tape my favorite

xcthulhu19:07:04

Okay, I'll post it here for people to playtest and then do the patch, since that's a lot of tedium for 15 lines of code

mpenet20:07:35

Seems like fmap appears twice in lazy-combinators call fyi

glv20:07:21

Just for fun, I’ve been prototyping a little library to generate EBNF-ish syntax diagrams from specs. Delving into the spec internal representation has been … eeeenteresting. 😕

xcthulhu20:07:58

@mpenet: Sounds like someone else is going to be signing something, issuing a JIRA ticket with a simple patch and waiting days and days for a fix

xcthulhu20:07:00

It's admittedly 16 lines....

bbrinck20:07:59

Any ideas as to why clojure.test/check would return the empty list?

user=>  (require '[clojure.spec :as s])
nil
user=> (s/fdef my+
  #_=>         :args (s/tuple number? number?)
  #_=>         :ret symbol?
  #_=>         :fn #(= (:ret %) (apply + (:args %))))
user/my+
user=> (require '[clojure.spec.test :as stest])
nil
user=> (stest/check 'my+)
()
user=>
Am I missing a step here?

bbrinck20:07:14

I have the following function defined (defn my+ [x y] (+ x y))

glv20:07:12

Use a backquote instead:

(stest/check `my+)

glv20:07:52

(That yields a fully namespace-qualified symbol.)

bbrinck20:07:58

ah, thank you!

Alex Miller (Clojure team)20:07:05

@mpenet thx, I’ll try to get that fixed the next time someone is paying attention

xcthulhu21:07:46

> Just for fun, I’ve been prototyping a little library to generate EBNF-ish syntax diagrams from specs. Delving into the spec internal representation has been … eeeenteresting. @glv: This is cool. Does it emit EBNF that instaparse can digest? I would absolutely love if I could shake SQL tables out of specs. But that's super hard and probably will never happen.

Alex Miller (Clojure team)21:07:08

why not model your data and shake specs and tables out of that?

glv21:07:51

No, but it’s super basic right now. I’m more concerned with deciphering the structures and building code that can navigate them. It’ll be easy to play with the details of what gets generated later. (Plus, Alex said that those internals are still likely to change.)

bsima23:07:15

can I spec tagged literals?

bsima23:07:26

I'm trying to spec a config file, like this: https://github.com/juxt/aero#profile

bsima23:07:19

nvm, the problem I'm having is that aero does its own eval of the config file, so I can't really get access to the data before the literals are processed (even if I do spec the config file before I pass it to aero, I don't have aero's data readers loaded, so #profile and such is unrecognized). The solution is to spec the data after aero processes it and replaces the tagged literals