1

I'm struggling with Clojure(script) spec. I slightly found out what part causes problem but I can't solve it.

(defn filter-ids
  [[items fields] _]
  (let [ids
        (for [item items
              field-tags (vals fields)
              :let [item-tags (-> item second :tags)
                    item-id (first item)]
              :when (and
                     (seq field-tags)
                     (empty? (set/difference field-tags item-tags)))]
          item-id)]
    (into #{} ids)))

Above code is what I tried to define spec. (fdef)

And I defined spec.

(spec/def :common/id (spec/and
                      keyword?
                      #(-> %1 name js/parseInt nat-int?)))

(spec/def :common/label string?)

(spec/def :common/tags (spec/coll-of string? :kind set?))

(spec/def :common/item (spec/keys :req-un [:common/label :common/tags]))


(spec/fdef filter-ids
  :args (spec/cat
         :useful (spec/cat
                  :items (spec/map-of :common/id :common/item)
                  :fields (spec/map-of :common/id :common/tags))
         :useless any?)
  :ret (spec/coll-of :common/id :kind set?))

And when I run it with instrument, error occurs.

(stest/instrument `filter-ids)


(filter-ids [{:0 {:label "task0" :tags #{"one" "two"}}}
             {:0 #{"three"}, :1 #{"one"}}]
            nil)


; Execution error - invalid arguments to taggy.states.subs/filter-ids at (<cljs repl>:1).
[{:0 {:label "task0", :tags #{"two" "one"}}} {:0 #{"three"}, :1 #{"one"}}] - failed: map? at: [:useful :items]

It seems like spec think first argument needs to be map, which is what I'm not intended to.

When I do like below, it doesn't complaining about map?. (although still a error because it's not valid at all)

(filter-ids {{:0 {:label "task0" :tags #{"one" "two"}}} 1
             {:0 #{"three"}, :1 #{"one"}} 2}
            nil)

I'm a newbie and really need some help to move on.

Thanks.

2
  • Unrelated to the problem: turning numbers into keywords is usually frown upon. Just use the number as key Commented Aug 4, 2021 at 12:01
  • @cfrick I think you are right. thanks you for sharing your thought. Commented Aug 4, 2021 at 12:10

1 Answer 1

3

spec/cat is a "sequence regex" and it "unrolls" if you nest it inside another spec/cat.

You can either wrap the inner spec/cat call in a spec/spec call, which prevents that unrolling, or you can switch to spec/tuple (and remove the :items and :fields labels):

(spec/fdef filter-ids
  :args (spec/cat
         :useful (spec/spec (spec/cat
                              :items (spec/map-of :common/id :common/item)
                              :fields (spec/map-of :common/id :common/tags)))
         :useless any?)
  :ret (spec/coll-of :common/id :kind set?))
;; or
(spec/fdef filter-ids
  :args (spec/cat
         :useful (spec/tuple
                  (spec/map-of :common/id :common/item)
                  (spec/map-of :common/id :common/tags))
         :useless any?)
  :ret (spec/coll-of :common/id :kind set?))

Both of those will work. Which you choose may depend on what information you want in your error messages (I think the former provides more context when you get something wrong because of the :items and :fields labels).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.