Fork me on GitHub
#clojure-spec
<
2017-11-09
>
rickmoynihan00:11:10

I think I’ve found a bug in clojure 1.9 / clojure.core.specs.alpha:

(if-let [foo 1] foo :clojure.spec.alpha/invalid)

CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/if-let did not conform to spec:
In: [2] val: :clojure.spec.alpha/invalid fails at: [:args :else] predicate: any?
 #:clojure.spec.alpha{:problems ({:path [:args :else], :pred clojure.core/any?, :val :clojure.spec.alpha/invalid, :via [], :in [2]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__1188 0xeabf22e "[email protected]"], :value ([foo 1] foo :clojure.spec.alpha/invalid), :args ([foo 1] foo :clojure.spec.alpha/invalid)}, compiling:(*cider-repl catalyst-graft*:8654:1) 

rickmoynihan00:11:12

issue appears to be that you can’t return the value :clojure.spec.alpha/invalid from macros. It’s kinda like a macro hygiene issue, but for s/invalid

bronsa00:11:49

it's known

rickmoynihan00:11:46

cool. Figured it would be

sushilkumar12:11:46

Hello everyone, I am learning Spec'ing higher order functions. Here are the examples which I am experimenting with (actual HOFs I am working on are quite complex) :

(Works fine with exercise-fn.)
(defn hof1 [x f] (f x))
(s/fdef hof1
        :args (s/cat :x number?
                     :f (s/fspec :args (s/cat :y number?)
                                 :ret number?))
        :ret number?
        :fn #(= ((-> % :args :f) (-> % :args :x)) (:ret %)))

(exercie-fn doesn't work :(. Here I am expecting input f to be either + or - or * fn.)
(defn hof2 [f x] (f x 1))
(s/fdef hof2
        :args (s/cat :x number?
                     :f (s/fspec
                          :args (s/cat :y number?)
                          :ret number?))
        :ret number?
        :fn #(= ((-> % :args :f) (-> % :args :x) 1) (:ret %)))
How write spec to ensure correctness in the samples generated using exercise-fn for HOFs? (I want to use spec's check fn to test HOFs on generated inputs based on written spec.)

Shantanu Kumar14:11:03

Hi, I’m a spec noob here — can somebody tell me an easy way to automatically (st/instrument) in all namespaces?

joost-diepenmaat14:11:03

you only need to call st/instrument once

joost-diepenmaat14:11:19

but you have to make sure you’ve loaded all the namespaces before you do

joost-diepenmaat14:11:53

if it’s in your dev setup, just call st/instrument in your main or dev namespace at the bottom

joost-diepenmaat14:11:11

if it’s in tests, your best bet is probably to use a fixture

Shantanu Kumar15:11:17

Also, how do I generally make “did not conform to spec” errors easier to read? I’m using Orchestra+Expound and have specified the following, but to no avail:

(st/instrument)
(set! s/*explain-out* expound/printer)
as mentioned here: https://github.com/bhb/expound#using-orchestra

bbrinck15:11:30

@kumarshantanu Can you post an example of the code you’re calling and the “did not conform to spec” message you get?

Shantanu Kumar15:11:46

This gist works for me at the REPL. @bbrinck The error reporting I’m dealing with extracts the exception message and stack trace and sends it as HTTP response. Is Expound supposed to work in that use case?

bbrinck16:11:35

Yes, it should. For instance, building on my previous example, here is code to catch the instrumentation exception and return the message as a string: (try (foo "") (catch Exception e (.getMessage e)))

bbrinck16:11:54

Another option is to use Expound to validate incoming data directly. For instance, let’s say the HTTP endpoint takes some JSON as incoming data

bbrinck16:11:06

Then you could validate that data with something like:

(binding [s/*explain-out* expound/printer]
  (s/explain-str :my-app/http-data-spec http-data))

bbrinck16:11:29

By default, Expound writes out all the Clojure specs that failed, but there is an (undocumented, but soon to be documented) option to omit this

bbrinck16:11:22

if you’re interested in that, I can give some example code

bbrinck18:11:35

If anyone else is interested, the issue was the dynamic var *explain-out* is only set in the current thread, and servers like HTTP Kit spawn new threads for requests. The fix is to replace (set! s/*explain-out* expound/printer) with (alter-var-root #'s/*explain-out* (constantly expound/printer))

naomarik17:11:11

there a better way of having a generator that just executes a function with no regard to the input? this does what i want, but notice the argument here is unnecessary #(gen/fmap (fn [_] (utils/uniform 1e12 1e13)) (s/gen pos-int?))

taylor18:11:54

I was going to suggest gen/return but do you even need the output of the pos-int? generator?

taylor19:11:37

for example, doesn’t matter what the previous generator is doing if you’re just constantly returning the same thing regardless of input

(gen/sample (gen/fmap (constantly -1) gen/pos-int))
=> (-1 -1 -1 -1 -1 -1 -1 -1 -1 -1)

gfredericks19:11:56

this is a nonstandard generator construction The "right way" would be to use combinators somehow rather than calling a function that supplies its own randomness. That might be a bit more effort, depending on what you want. If you aren't doing things the "right way", then there's not really a better way.

gfredericks19:11:11

do you really need a uniform distribution between two doubles?

gfredericks19:11:47

(gen/double* {:min 1e12 :max 1e13}) would not be very nonuniform I don't think

naomarik05:11:41

@U3DAE8HMG yeah my point was i didn’t need the output of the pos-int? generator. gen/return was EXACTLY what i was looking for, thanks 😉

naomarik05:11:29

@U0GN0S72R yeah i need uniform distribution between two large numbers because made a spec that expected a number beteween those two ranges and this issue causes it to fail without a custom generator https://dev.clojure.org/jira/browse/CLJ-2179

naomarik05:11:02

i also stumbled upon #(gen/choose 1e12 1e13) that does uniform distribution

gfredericks12:11:06

that ticket is interesting

gfredericks12:11:46

@U0DHHFEDP what is your spec for exactly?

gfredericks12:11:54

I'm having trouble seeing why something would fail

naomarik13:11:05

@U0GN0S72R it’s a ghetto spec for a unix timestamp in ms, the generator fails because it attempts to make ints starting at very low numbers. here’s the code

(s/and pos-int?
          (s/int-in 1e12 1e13))

naomarik13:11:04

it passes most the time when ran by itself but i have it nested in another datastructure and it fails 100% of the time

gfredericks13:11:37

oooooh; this is an s/and issue, not an s/int-in issue

gfredericks13:11:56

if you swap the order of those two you should have it succeed

gfredericks13:11:20

or set the generator to be the default generator that the s/int-in expression generates

gfredericks13:11:37

I think CLJ-2179 is a red herring here

naomarik13:11:38

tried swapping the order, didn’t work

naomarik13:11:12

i think the issue is legit, if you try running the code (gen/sample (s/gen (s/int-in 0 100))) you get a ton of low numbers

naomarik13:11:23

(0 0 0 1 0 4 0 52 3 34)

naomarik13:11:27

things like this

naomarik13:11:17

my final code works well enough though (s/with-gen (s/and pos-int? (s/int-in 1e12 1e13)) #(gen/choose 1e12 1e13) ))

naomarik13:11:55

sorry for formatting 😉

gfredericks17:11:22

@U0DHHFEDP getting low numbers that are in the range is one thing, but I thought you were describing getting numbers that were out of range

naomarik02:11:43

@U0GN0S72R that’s what I thought was going on, i think my issue is that i was plugging in doubles using the short notation. It seems (s/int-in) doesn’t cast those for you but will verify that the input is in range, this is why I needed to pair it with pos-int

naomarik02:11:19

;; Fails reliably
(s/def ::test2 (s/and pos-int? (s/int-in 1e12 (- 1e13 1))))

;; Fails sometimes
(s/def ::test3 (s/and pos-int? (s/int-in 1e9 (- 1e10 1))))

(s/def ::works (s/int-in (encore/as-int 1e13) (encore/as-int (- 1e14 1))))

naomarik02:11:28

bumping the numbers up in the first example makes it fail a lot more often, I guess pos-int is the only thing generating numbers here?

naomarik02:11:31

in any case was my fault for plugging doubles in something that is clearly for ints 😉

gfredericks12:11:25

yeah, when you use s/and the generator is taken from the first spec and the remaining specs are used to filter