Schema for a function with keyword arguments

44 views
Skip to first unread message

Rovanion Luckey

unread,
May 2, 2017, 9:14:36 AM5/2/17
to Plumbing and Graph: the Clojure utility belt
Hi,
I'm sorry to bombard the mailing list but I've been stuck on this one problem for the whole day. Say we have a function `get-ints` with one positional argument, the number of ints the caller wants, and two named arguments `:max` and `:min` like:

    ;; Ignore that the implementation of the function is incorrect.
    (defn get-ints [nr & {:keys [max min] :or {max 10 min 0}}]
      (take nr (repeatedly #(int (+ (* (rand) (- max min -1)) min)))))

    (get-ints 5)               ; => (8 4 10 5 5)
    (get-ints 5 :max 100)      ; => (78 43 32 66 6)
    (get-ints 5 :min 5)        ; => (10 5 9 9 9)
    (get-ints 5 :min 5 :max 6) ; => (5 5 6 6 5)


How does one write a Plumatic Schema for the argument list of `get-ints`, a list of one, three or five values where the first one is always a number and the following items are always pairs of a keyword and an associated value?

I've tried to approach this problem a couple of different ways, but it all comes back to the fact that I can't find a way to concatinate two "pairs" or "sequences" to each other without them defining their own subsequences.

    (def kwargs [(schema/optional (schema/pair (schema/enum :max) "max-key" schema/Int "max-val") "arg1")
                 (schema/optional (schema/pair (schema/enum :min) "min-key" schema/Int "min-val") "arg2")])

    (schema/validate kwargs [[:max 10] [:min 5]]) ; => [[:max 10] [:min 5]]
    (schema/validate kwargs [[:max 10]])          ; => [[:max 10]]
    (schema/validate kwargs [:max 10 :min 5])     ; => Exception

This seems related to an old SO-question on the same subject [0] that has stood unanswered for quite a while, I also posted my question on there earlier today [1].


[0]: http://stackoverflow.com/questions/29013875/how-to-define-prismatic-schema-for-interleaved-types
[1]: http://stackoverflow.com/questions/43733676/plumatic-schema-for-keyword-arguments

Jason Wolfe

unread,
May 2, 2017, 12:45:31 PM5/2/17
to Plumbing and Graph: the Clojure utility belt
No worries.  So, we (prismatic) basically frowned on keyword rest arguments internally, preferring to always pass an explicit map (not as a rest arg).  I can go into the reasons if you find it interesting, but basically that + the required additional complexity means that schema does not  support schemas for rest keyword arguments.  

https://github.com/plumatic/schema/blob/master/src/cljx/schema/core.cljx#L1325


I know this answer is probably unsatisfying, but it's unlikely to change at this point.  Apologies this wasn't clear from the docs.  (If you want 100% support for everything Clojure can do, I would recommend looking into clojure.spec.)

Best,
Jason  

Rovanion Luckey

unread,
May 3, 2017, 11:30:30 AM5/3/17
to Plumbing and Graph: the Clojure utility belt
Thank you for the quick and clear answer! It's of course unsatisfying to have to step out of Schemas DSL, but it's not as if I've produced anything better myself so I can't exactly complain.

For posterity I'll leave the solution I came up with during the day:

(defn key-val-seq?
  ([kv-seq]
   (and (even? (count kv-seq))
        (every? keyword? (take-nth 2 kv-seq))))
  ([kv-seq validation-map]
   (and (key-val-seq? kv-seq)
        (every? nil? (for [[k v] (partition 2 kv-seq)]
                       (if-let [schema (get validation-map k)]
                         (schema/check schema v)
                         :schema/invalid))))))

(def get-int-args
  (schema/constrained
   [schema/Any]
   #(and (integer? (first %))
         (key-val-seq? (rest %) {:max schema/Int :min schema/Int}))))

(schema/validate get-int-args '())               ; Exception: Value does not match schema...
(schema/validate get-int-args '(5))              ; => (5)
(schema/validate get-int-args [5 :max 10])       ; => [5 :max 10]
(schema/validate get-int-args [5 :max 10 :min 1]); => [5 :max 10 :min 1]
(schema/validate get-int-args [5 :max 10 :b 1])  ; Exception: Value does not match schema...


Reply all
Reply to author
Forward
0 new messages