Fork me on GitHub
#clojure-spec
<
2020-04-21
>
alex01:04:03

I'm having some trouble with adding a spec for a keyword which is supposed to represent a function. I tried this

(s/fdef ::on-change
  :args (s/cat :value :option/id))

(s/def ::props (s/keys :req-un [::options
                                ::value]
                       :opt-un [::class
                                ::centered?
                                ::on-change
                                ::variant]))

(s/fdef tabs
  :args (s/cat :props ::props))

alex01:04:20

> Var clojure.test.check.properties/for-all* does not exist, clojure.test.check.properties never required

alex01:04:54

from trying to pass ::on-change to fdef

alex01:04:43

After reading the API docs, it seems fdef can only take a symbol naming a function. My question is... how can I create a spec for the function that is passed as :on-change?

robertfw01:04:52

It's funny you ask, I was just arriving to ask something similar. I have a spec describing a map, of which some keys are functions. I'd love to be able to specify that those functions should conform to a given fdef, but not sure how to accomplish that. As is, I just have the keys referring to the functions specced using fn?

alex01:04:35

Now I don't feel too silly asking 🙂

Alex Miller (Clojure team)01:04:20

you can use fspec to do this as well, but there are some big caveats for generation

Alex Miller (Clojure team)01:04:43

personally, I have not found it to be worth doing fspec over ifn?

alex01:04:36

@alexmiller Does ifn? allow you to specify the shape of the arguments that are passed in? I can't seem to find any examples doing so

Alex Miller (Clojure team)01:04:17

you can do that with fspec (same args as fdef)

alex01:04:23

Tried a simple fspec but that doesn't seem to compile. Hmm

(s/fspec ::on-change
         :args (s/cat :value number?)
         :ret any?)

kenny02:04:14

Remove the first arg:

(s/fspec :args (s/cat :value number?)
         :ret any?)

alex03:04:43

Thanks! How do I associate it to the keyword which will use the spec?

sgepigon11:04:35

(s/def ::on-change
  (s/fspec :args (s/cat :value number?)
           :ret any?))

alex14:04:06

I get this error when I try that. Is this expected? > Uncaught Error: Var clojure.test.check.properties/for-all* does not exist, clojure.test.check.properties never required

kenny14:04:43

fspec uses test.check to ensure the correctness of the spec'ed input.

alex23:04:08

Thank you!

Aron07:04:13

how can I spec transient things like values of let bindings?

Ben Sless09:04:37

It might be terrible but I recently had a similar issue, went for this solution:

(let [x (expr ...)]
  (eval
   `(s/def ::my-spec
      (s/keys
       :req-un
       [~x]))))

Aron09:04:50

😄

Aron09:04:00

i am not sure about terrible but it's scary

Ben Sless09:04:58

it is. If you can convince yourself the expr doesn't do any IO then you can sleep well imo

Ben Sless09:04:57

In my case I had to create a qualified keyword who's name starts with a number. seems like :1foo is a valid kw but :user/1foo can't be read by the reader. (keyword "user" "1foo") works

Aron09:04:47

this is supposed to run in a browser 🙂

Aron07:04:05

I see the suggestion to use s/assert in docs, but isn't that also something that I should only do in development and not make it part of the code that might be shipped eventually?

Alex Miller (Clojure team)12:04:34

Spec asserts can be turned off and even compiled out in prod code

Aron12:04:24

thanks, I realized there has to be some solution because there were blogposts about how to use it, but now I know where to look for it.

Ben Sless09:04:03

Is it possible to spec/validate java collections? this naive example doesn't work

(s/def ::foo int?)
(s/def ::bar string?)
(s/def ::m (s/keys :req [::foo ::bar]))

user=> (s/valid? ::m {::foo 1 ::bar "2"})
true
user=> (s/valid? ::m (java.util.HashMap. {::foo 1 ::bar "2"}))
false

Alex Miller (Clojure team)12:04:48

Spec does not cover Java collections so you’d have to pour one into a Clojure collection first

Ben Sless17:04:32

This is probably a terrible idea, but I guess everything is possible if you try hard enough

Ben Sless17:04:24

It would be nice, maybe in spec2, if the meanings of maps and sequences were relaxed a bit. But I'm coming at it from an esoteric use case

Alex Miller (Clojure team)17:04:44

that seems way harder than converting the HashMap into a Clojure map

Alex Miller (Clojure team)17:04:05

we do not have plans to support Java colls in spec 2

Alex Miller (Clojure team)17:04:33

(s/valid? ::m (into {} (java.util.HashMap. {::foo 1 ::bar "2"})))

Ben Sless17:04:51

That's the trivial case, in reality I might be dealing with an arbitrarily nested java collection 😞

Ben Sless18:04:26

It's also unfortunate because it creates a bit of a mismatch between the validation mechanism and the applied predicates. In this example, get works on java.util.Map, so does seq, so conform* can do its work. The only "hurdle" is map?, which is a pretty stringent requirement. Why not expand spec a bit more towards reflecting intent and less implementation?

Alex Miller (Clojure team)18:04:55

the intent is to validate clojure data

Alex Miller (Clojure team)18:04:13

validating java colls was out of scope for us

eval202016:04:39

What spec could validate that a map contains at least one true value?

Alex Miller (Clojure team)16:04:23

any function that takes a value can be a spec

❤️ 4
Alex Miller (Clojure team)16:04:46

so rephrasing - what function could validate that a map contains at least one true value?

Alex Miller (Clojure team)16:04:55

write that function and you're done

eval202016:04:28

Ah, I was overthinking this - thanks