Fork me on GitHub
#clojure-spec
<
2017-12-13
>
johanatan04:12:23

hi, does anyone know how to diagnose the following error?

1. Unhandled java.lang.IllegalArgumentException
   No implementation of method: :specize* of protocol:
   #'clojure.spec.alpha/Specize found for class: nil

johanatan04:12:15

i found this but it seems only tangentially related: https://dev.clojure.org/jira/browse/CLJ-2032

johanatan04:12:26

here's the code:

(s/def ::json-primitive
  (s/or :n nil :b boolean? :n number? :s string?))

(s/def ::json-structure
  (s/with-gen
    (let [checker (fn inner [primitives? v]
                    (cond
                      (map? v) (and (every? string? (keys v)) (every? (partial inner true) (vals v)))
                      (coll? v) (every? (partial inner true) v)
                      primitives? (s/valid? ::json-primitive v)
                      :else false))]
      (partial checker false))
    #(gen/recursive-gen (fn [inner] (gen/one-of [(gen/list inner) (gen/vector inner) (gen/map gen/string inner)]))
                        (s/gen ::json-primitive))))

(s/def ::validation-fn
  (s/or :func (s/fspec :args (s/cat :x ::json-structure)
                       :ret boolean?)
        :spec s/spec?))

(s/def ::message-fn
  (s/fspec :args (s/cat :x ::json-structure)
           :ret string?))

(defn- seq->gen
  "Takes a sequence of generators and produces a generator of sequences."
  [seq]
  (apply gen/tuple seq))

(defn- map-seq->gen
  "Takes a sequence of values and a function to apply over them
  and produces a generator of the sequence of mapped values."
  [f val-seq]
  (seq->gen (map f val-seq)))

(s/def ::refinements
  (s/with-gen
    (s/map-of keyword? (s/tuple (s/nilable keyword?) (s/tuple ::validation-fn ::message-fn)))
    #(gen/let [kwds (gen/vector gen/keyword 5 25)
               refinements
               (map-seq->gen
                (fn [kwd]
                  (gen/let [k (gen/frequency [[4 (gen/elements (clojure.set/difference (set kwds) (set [kwd])))]
                                                [1 (gen/return nil)]])
                            validation-fn (gen/frequency [[8 (gen/return (with-meta (fn [_] true) {:validates? true}))]
                                                          [1 (gen/return (with-meta (fn [_] false) {:validates? false}))]])
                            message-fn (gen/fmap (fn [s] (with-meta (fn [_] s) {:msg s})) (s/gen string?))]
                    [kwd [k [validation-fn message-fn]]]))
                kwds)]
       (into {} refinements))))
and the invocation:
(gen/sample (s/gen ::refinements) 1)

johanatan04:12:58

you'll need appropriate imports; i.e., s and gen from the spec guide

lopalghost13:12:59

(s/def ::json-primitive (s/or :n nil 😛 boolean? :n number? :s string?))

lopalghost13:12:28

should that be nil?

guy13:12:50

nil? is a predicate whereas nil is not

guy13:12:59

is that what you mean?

guy13:12:20

(s/or :n nil? :b boolean? :n number? :s string?))

guy13:12:46

i think s/or takes keyword, predicate pairs

lopalghost13:12:53

yes, this was in response to @johanatan

guy13:12:19

ah sorry

guy13:12:26

:thumbsup:

guy13:12:40

Good spot then!

johanatan17:12:20

does anyone know a way for an fspec to consider relations between its target's inputs? like if one of the parameters is functionally constrained by the value of the other one?

johanatan17:12:16

i could do it by stuffing the args into a nested vector:

(defn f [[a1 a2]] ...)
and then writing a spec/generator for that vector which combines two other [independent] spec/generators but i was kind of hoping that there is a better story for this...

lopalghost17:12:51

the :args argument already treats the args as a sequence, so you can use (s/and ...) to spec both individual inputs and relations between them

johanatan17:12:26

ah, nice! thx again

jcthalys18:12:33

Hi, how can i let this second element of a tuple optional?

jcthalys18:12:41

(s/tuple ::string-not-blank
                                          (s/nilable pos-int?))

jcthalys18:12:34

this is accepted ["ChLh2" nil] but this not ["ChLh2"]

lopalghost18:12:22

tuple is for a collection of fixed size. for variable size you want to use cat

lopalghost18:12:21

eg

(s/cat :string ::string-not-blank :number pos-int?)

lopalghost18:12:30

sorry, that should be

(s/cat :string ::string-not-blank :number (s/? pos-int?))

lopalghost18:12:38

if you want the second term to be optional

jcthalys19:12:49

in my case it’s a vactor, with one string and when have another thing its that int…

jcthalys19:12:32

that’s s/cat return a map…

lopalghost19:12:40

after you conform vector, you can use unform to turn it back into a sequence

lopalghost19:12:53

keep in mind you don't need to use conform at all

lopalghost19:12:29

the alternative would be something like:

(s/or :two (s/tuple ::string-not-blank) 
              :one (s/tuple ::string-not-blank pos-int?))

lopalghost19:12:23

or otherwise, pad the vector with a nil before conforming, if it only has one element

lopalghost19:12:37

I think cat would be most idiomatic though

johanatan20:12:49

@lopalghost actually s/and for that purpose will result in a such-that and you can be at risk of a couldn't satisfy such-that predicate after 100 tries error

johanatan20:12:08

there needs to be a way to generate the two values together so that we know the two will conform with one another

johanatan20:12:14

hence my suggestion to use a nested vector of args

johanatan20:12:30

do you see any other way?

tristefigure20:12:03

Hi. This is my first time using spec, and taking inspiration from clojure.core.specs.alpha, I decided to write my own version to parse/produce macro definitions. I wanted it to return something less detailed in a flatter hash and I wasn't happy with the fact results from conform mirror the spec' structure and with other petty details, so this is what I came up with:

tristefigure20:12:46

To which extent is this a misuse of this library ?

lopalghost21:12:55

@johanatan not sure how a nested vector would solve the problem, as any spec you apply to the nested vector could also be applied to :args

johanatan21:12:23

@lopalghost it solves it by letting me introduce a generator specifically for that tuple

lopalghost21:12:13

You can't do that with the :arts spec? Sorry if I'm misunderstanding you

johanatan21:12:51

e.g.,

(s/def ::args-tup
  (s/with-gen
    (s/tuple ::arg1 ::arg2)
    #( ... enforce relations here )))

(s/fdef -refinement-kwd->validator
        :args (s/cat :tup ::args-tup)
        :ret ::ret-val)
(defn- -helper [[arg1 arg2]] ; tuple allows both inputs to be generated simult.
  ;; omitted
)

(defn- original [arg1 arg2]
  (-helper [arg1 arg2]))

johanatan21:12:19

you can do it in :args but you are liable to hit the cannot satisfy such-that problem

lopalghost21:12:59

I mean why not just use ::args-tup as the :args spec? Does that cause a problem? I'd try it out myself but I don't have a repl at the moment

johanatan21:12:13

s/cat is required to name the arg

johanatan21:12:26

yea, without s/cat, stest/check tries to send in the wrong number of arguments

johanatan21:12:41

[i.e., sends in 2 where 1 is expected]

lopalghost22:12:11

ok I cobbled together a stupid example, let me know if this is relevant:

(s/def ::args-tup (s/and (s/tuple pos-int? pos-int?)
                         #(> (first %) (second %))))

(s/fdef subtract
        :args ::args-tup
        :ret pos-int?)

(defn subtract
  [high low]
  (- high low))

lopalghost22:12:14

didn't supply a generator of course, but stest/check works with no problems here

hlolli22:12:59

Would it be wise to spec no argument, if just for the metadata, if so, how would someone spec :args for no args?

curlyfry06:12:26

Just (s/cat)!

hlolli10:12:34

Thusund takk!

johanatan22:12:11

@lopalghost that use of s/and has an implied underlying gen/such-that which depending on circumstances may be impossible or too difficult to satisfy. thus it is always better to generate exactly the right structure you want from scratch rather than relying on such-that.

johanatan22:12:19

@lopalghost also, your fdef won't work: :args is an alternating sequence of name and type/spec. thus you need s/cat