Fork me on GitHub
#clojure-spec
<
2016-10-13
>
tianshu06:10:28

It seems clojure.spec/assert always return original data in clojurescript.

tianshu06:10:04

(s/assert (s/keys :req-un [::a]) {:b 10})
return {:b 10}

Oliver George09:10:07

@doglooksgood asserts aren't enabled by default. Try (s/check-asserts true).

yenda09:10:55

is there a recommended place to put (s/check-asserts true) ?

jeroenvandijk09:10:07

Is this a bug?

(def my-keys [:my.ns/some-key])
(s/def :my.ns/ok (s/keys :req-un ~my-keys)) 
;=> :my.ns/ok
(s/def :my.ns/not-ok (s/keys :opt-un ~my-keys))
; CompilerException java.lang.AssertionError: Assert failed: all keys must be namespace-qualified keywords

jeroenvandijk09:10:01

some specific behaviour for :opt-un, I’ll try to dig further

jeroenvandijk11:10:59

After looking a bit further I fell into a macro trap. I guess at most it is inconsistent behaviour

jeroenvandijk12:10:08

This terrible hack does what I want

(defmacro dynamic-keys [& opts]
  (let [args (mapcat (fn [[k v]]
                       [k v])
                     (partition 2 opts))]
    `(eval (list `s/keys ~@args))))

jeroenvandijk12:10:19

Is there a better way?

odinodin12:10:56

what is the preferred convention for fdef specs, should they come before or after the function they spec?

darwin12:10:53

@jeroenvandijk you could write a macro which emits my-keys

darwin12:10:58

but your solution is fine, under given circumstances, I think it leads to more-readable code

darwin12:10:46

instead of eval you could use ns-resolve and var-get, if you expected just a symbol as arg, I think

darwin12:10:22

eval could be a weapon of mass destruction 🙂

immo12:10:50

@jeroenvandijk You don’t necessarily need macros. I’ve been using these to create specs dynamically:

jeroenvandijk12:10:39

ah yeah could just use eval, slightly better

mlimotte20:10:09

Are there any examples of how to instrument with a :spec override? For example:

(defn foo [x] (inc x))
(s/def ::x clojure.future/pos-int?)
(s/fdef foo :args (s/cat :x ::x))
; Attempt #1
(stest/instrument `foo {:spec {`foo {:args (s/cat :x zero?)}}})
(foo 1)
=> 2
; Attempt #2
(stest/instrument `foo {:spec {::x zero?}})
(foo 1)
=> 2

clojuregeek20:10:57

I want to say that my function returns a vector of 1 or more jobs (a map) is this correct?

30 │ (s/def ::job (s/keys :req [::type ::meta]
  31 │                      :opt [::payload]))
  32 │
  33 │ (s/fdef sample-jobs
  34 │   :args int?
  35 │   :ret (s/+ ::job))

mlimotte21:10:17

@clojuregeek I think you need to use (s/coll-of ...) for :ret

clojuregeek21:10:56

hmm .. i would expect a better doc string?

kraken-consumer.job/sample-jobs
       ([] [x])
       Spec
         args: int?
         ret: (every :kraken-consumer.job/job :clojure.spec/cpred #function[kraken-consumer.job/fn--12178] \
       :clojure.spec/kind-form nil :clojure.spec/conform-all true)

mlimotte21:10:12

Found the answer to my instrument with :spec question above. Should be done like this:

(defn foo [x] (inc x))
(s/def ::x clojure.future/pos-int?)
(s/fdef foo :args (s/cat :x ::x))
(stest/instrument `foo {:spec {`foo (s/fspec :args (s/cat :x zero?))}})
(foo 1)
=>
ExceptionInfo Call to #'user/foo did not conform to spec:
In: [0] val: 1 fails at: [:args :x] predicate: zero?
:clojure.spec/args  (1)
:clojure.spec/failure  :instrument
:clojure.spec.test/caller  {:file "form-init5306699344209214826.clj", :line 5, :var-scope user/eval24489}
  clojure.core/ex-info (core.clj:4617)

clojuregeek21:10:17

using + has a decent doc string

175 │ kraken-consumer.job/sample-jobs
 176 │ ([] [x])
 177 │ Spec
 178 │   args: int?
 179 │   ret: (+ :kraken-consumer.job/job)
 

danielcompton22:10:24

Is there a way to spec a sequential collection? s/coll-of throws a massive error if you pass a map to it, as it tries to check each mapentry against a spec and displays a failure for each mapentry

jrheard22:10:26

s/map-of might be useful for you

danielcompton22:10:51

I want my spec to be a sequence of values, each conforming to a spec

danielcompton22:10:06

If I pass a map, then each mapentry gets evaluated against that spec instead

jrheard22:10:40

would you mind pasting an example?

jrheard22:10:04

s/cat is a good way of speccing sequences, but you’re probably already familiar with it so i hesitate to mention it

jrheard22:10:20

i haven’t seen the behavior you describe re: coll-of and maps

jrheard22:10:24

am very curious 🙂

danielcompton22:10:12

I thought about s/cat but didn't think it was for this use case? I could be wrong though

jrheard22:10:44

if i’m reading this right, you’re passing in {a-map}, and not [{a-map}] - could that be the issue?

jrheard22:10:49

rereading, i’ve probably missed something 🙂

danielcompton22:10:54

I know what the issue is, I'm saying that the spec error message telling me that wasn't very useful when I used s/coll-of, so I was wondering if there was something else?

Alex Miller (Clojure team)22:10:56

coll-of seems like what you want although I'm not entirely positive I understand what you're doing

danielcompton22:10:27

I want my spec to be a sequential? collection of ::request-maps. I define ::request-map, then create (s/def ::request-maps (s/coll-of ::request-map)). If I pass "abc" and check it against my spec, I get

{:cljs.spec/problems
 [{:path [],
   :pred coll?,
   :val "abc", 
   :via [:day8.re-frame.http-fx/request-maps], 
   :in []}]}
as it fails on the coll? predicate. This is a pretty easy to understand problem. However if I check a single map against this spec, I get a very large error, because the map is treated as a sequence, and each MapEntry in the map is checked against (and fails) my spec ::request-map.

danielcompton22:10:02

I can change my spec to (s/and sequential? (s/coll-of ::request-map)) but this seems like a fairly common use case, and wondered if there was another way to spec this more precisely?

hiredman23:10:02

use the regex ops, + or *

hiredman23:10:19

(s/* ::request-map)

hiredman23:10:49

(I mean, I don't know, that is just a suggestion, seems nicer than coll-of)

danielcompton23:10:53

That's a bit better, thanks. It short circuits earlier, but the error message is still not very illuminating:

{:cljs.spec/problems
 [{:path [],
   :pred map?,
   :val [:method :post],
   :via
   [:day8.re-frame.http-fx/request-maps
    :day8.re-frame.http-fx/request-maps
    :day8.re-frame.http-fx/request-map
    :day8.re-frame.http-fx/request-map], 
   :in [0]}]}

danielcompton23:10:24

It still treats a map as a sequence of map entries

Alex Miller (Clojure team)23:10:54

I think coll-of is the best match for what you are trying to say

Alex Miller (Clojure team)23:10:36

Using s/* by itself without other regex ops is less good

Alex Miller (Clojure team)23:10:01

As it conveys a sequential with more interesting internal structure

danielcompton23:10:07

Does it make sense to include maps as valid coll-of's?

Alex Miller (Clojure team)23:10:26

They are a collection of tuples

danielcompton23:10:27

Is there scope to add a sequential-of? or something similar?

Alex Miller (Clojure team)23:10:49

No, you could use :kind though

danielcompton23:10:20

That's wha I was after

Alex Miller (Clojure team)23:10:33

You may also want :into []

Alex Miller (Clojure team)23:10:08

Can't remember how it will conform without that

Alex Miller (Clojure team)23:10:25

That might already be the default

Alex Miller (Clojure team)23:10:29

That's used by gen too if you care about that so might double check that too

danielcompton23:10:55

docs seem to suggest an :into is needed as well here, as I don't know if sequential? can generate?