Fork me on GitHub
#clojure-spec
<
2018-02-24
>
Peter Wilkins08:02:32

Hi - I'm learning spec. I've googled and doc'ed but can't find a clue how to spec the sum of all numbers in a collection (a map) in this case

(s/def ::percentage (s/and double? #(>= % 0) #(<= % 100)))
(s/def ::scores (s/map-of keyword? ::percentage))

(s/valid? ::percentage  50.05)
(s/valid? ::scores {:a 70.49 :b 20.21 :c 9.29})
I'd like to confirm that the sum of ::scores = 100

robert-stuttaford09:02:24

#(= 100 (apply + (vals %))) surely?

yogidevbear10:02:11

Does anyone here have an open source repo where they're using spec? I feel like I'm missing something fundamental about spec and I'm hoping that looking at a few good examples of it being used within a project might help solidify some of the core concepts for me.

jannis10:02:10

@yogidevbear We wrote https://github.com/functionalfoundry/entitydb some time ago. Starting from workflo.entitydb.core/empty-db, most functions and input/output data have specs to allow random testing and integrity checking.

Peter Wilkins10:02:45

@robert-stuttaford sorry I worded the question badly. I can't figure out how to compose a spec using map-of and

#(= 100 (apply + (vals %)))
.
(s/def ::scores (s/and (s/map-of keyword? ::percentage) #(= 100 (apply + (vals %)))))
doesn't work. Also, wouldn't we want to be the kind of community to support and encourage beginners, surely?

luskwater12:02:55

@poppetew

(defn approx=
  "Return a fn that checks that a value is approximately `n` (within `e`)"
  [n e]
  #(-> (apply + (vals %))
       (- n)
       (Math/abs)
       (< e)))

(s/def ::percentage (s/and double? #(>= % 0) #(<= % 100)))
(s/def ::scores
  (s/and (s/map-of keyword? ::percentage)
         (approx= 100 0.1)))
(s/def ::scores-restrictive
  (s/and (s/map-of keyword? ::percentage)
         (approx= 100 0.001)))

(s/valid? ::percentage  50.05)

(def the-map {:a 70.49 :b 20.21 :c 9.29})
(apply + (vals the-map))
(s/valid? ::scores the-map)

(s/valid? ::scores-restrictive the-map)

luskwater12:02:10

Actually, @poppetew, your (s/and ,,, #(= 100 (apply + (vals %)))) would work, if you were using integers. That’s why we needed the approx= fn up there.

luskwater12:02:10

Your e may vary, of course. 🙂

luskwater13:02:11

(def the-map {:a 70.49, :b 20.21, :c 9.29})
=> #'cognitect.transcriptor.t_1/the-map

(apply + (vals the-map))
=> 99.98999999999998

(s/valid? :cognitect.transcriptor.t_1/scores the-map)
=> true

(s/valid? :cognitect.transcriptor.t_1/scores-restrictive the-map)
=> false

robert-stuttaford17:02:09

@poppetew i apologise if my rapidly typed response wasn’t suitably welcoming for you - i figured something would be better than nothing. i recognise now that it could have come across as condescending - not my intent! looks like @luskwater provided a far more considered response, which has taught me something!

robert-stuttaford17:02:39

@poppetew i am incredibly supportive of beginners 🙂

luskwater17:02:02

Wouldn't have had an answer for @poppetew so quickly, had it not been for @robert-stuttaford and his initial suggestion. Collaboration across timezones takes a little time itself.