Fork me on GitHub
#clojure-spec
<
2018-03-02
>
jimbob17:03:24

I don’t really understand why Cloujre spec is any better than simple pre conditions for simple validation. example:

(defn mk-protobuf
  [{:keys [reward-value available event-uri event-name event-at schema revision
           hostname fake environment shopper-uri service-agent shopper-name reward-uri reward-name action-at]
    :or   {service-agent service-agent
           hostname      (hostname)
           environment   (str env)
           event-at      (c/to-epoch (t/now))
           schema        (str schema)}}]
   :pre  [(string? event-uri)
          (string? reward-uri)
          (string? shopper-uri)
          (string? reward-value)
          (instance? Boolean available)]
won’t these preconditions be sufficient run time validation? what willl i get from spec that i cant get from these?

Alex Miller (Clojure team)17:03:59

docs, generated examples, automated generative testing

jimbob17:03:16

i suppose so, but is not these pre conditions and or conditions documentation enough?

jimbob17:03:23

and much fewer lines of code

bbrinck17:03:43

@ben.borders what’s the error message if you pass in something incorrect? What if one of the args was not just a string, but, say, a vector of strings?

jimbob17:03:47

(assuming we are not doing generative testing)

jimbob17:03:07

its not terrible

jimbob17:03:14

ex:

Exception in thread "async-dispatch-12" java.lang.AssertionError: Assert failed: (integer? event-uri)

jimbob17:03:24

but yes not as good as spec errors.

bbrinck17:03:06

I think the different may be starker if you pass in nested data e.g. a vector of maps that must contain keys

bbrinck17:03:55

but for simpler predicates like string? (as in your example), the differences in error messages won’t be as significant

jimbob17:03:40

sure, but then couldnt i just use clojure.test/is ?

nha17:03:09

I don’t recall what exactly but this ended up causing trouble for me down the line (maybe it was nested is? I don’t remember now)

jimbob17:03:51

and get things like

;FAIL in clojure.lang.PersistentList$EmptyList@1 (scratch.clj:5)
;expected: (string? s1)
;actual: (not (string? 10))

bbrinck17:03:18

Yes, although it becomes a little less clear for nested data e.g. a vector of strings

bbrinck17:03:28

or vector of maps of specific keys

jimbob17:03:06

thanks for your responses

bbrinck17:03:02

@ben.borders np. here’s an example of error message with is vs explain (using expound) https://gist.github.com/bhb/44d68a7ab878187b68d4fd48579d591b

bbrinck17:03:38

I copied and pasted from my REPL session and omitted my mistakes, so hopefully I didn’t miss anything important 😉

jimbob17:03:19

so then to get the same desired runtime validation, would it be advised to place s/valid? in the :pre block of the function? or are there better ways to do this?

jimbob17:03:48

i’ll probably end up exploring this.. it does indeed seem to be much better self documenting

bbrinck17:03:39

there are a few options. You can use fdef, or you could use a s/assert

jimbob17:03:08

mm alright

bbrinck17:03:42

for s/assert, keep in mind you’ll need to run (s/check-asserts true)

bbrinck17:03:14

you could also use s/explain-str and raise an exception

bbrinck17:03:34

(if the result is not literally "Success!\n")

jimbob17:03:05

gotcha. and for speccing the example map above, would i have to create an s/def for every key in the map? for example some of the keys in the map should contain the same type and structure of data, so it would be nice to reuse something like

(s/def ::non-empty-string not-empty string?)
for multiple keys instead of
(s/def thing1 ::nonempty-string) (s/def thing2 ::Nonempty-string

Alex Miller (Clojure team)17:03:21

yes, you will need one for every key

Alex Miller (Clojure team)17:03:44

that puts it in the spec registry which is used by s/keys

jimbob17:03:56

ok, gotcha thanks alex

Alex Miller (Clojure team)17:03:02

if you have many repetitive such things, macros can help

jimbob17:03:08

right, cool

Alex Miller (Clojure team)17:03:22

maybe eventually we’ll provide something to def multiple things at a time