(def valid-name? "Returns truthy if argument is a non-namespaced symbol." (every-pred symbol? (complement namespace))) (defn parse-arglist "Given an `arglist` (a vector of parameter names for a function method), returns a map of information about the method. May throw an exception if `arglist` cannot be parsed." [arglist] (assert (every? valid-name? arglist) "All parameter names must be non-namespaced symbols.") (let [variadic? (= (last (butlast arglist)) '&) fixed-args (cond->> arglist variadic? (drop-last 2))] {:fixed-args fixed-args :fixed-arity (count fixed-args) :rest-arg (when variadic? (last arglist)) :variadic? variadic?})) (defn arity-info "Given a seq of `arglists` (maps of information obtained via `parse-arglist` about methods of a particular function), returns a map of information about the function itself. May throw an exception if two or more `arglists` are mutually incompatible and therefore cannot coexist on the same function." [arglists] (assert (apply distinct? (map (juxt :fixed-arity :variadic?) arglists)) "No two methods of a single function may share the same arity.") (let [max-fixed-arity (apply max (map :fixed-arity arglists)) [variadic-arglist & more] (filter :variadic? arglists)] (assert (empty? more) "A function may have at most one variadic method.") (assert (= (:fixed-arity variadic-arglist) max-fixed-arity) (str "A variadic method may not take fewer parameters than a " "fixed-arity method of the same function.")) {:arglists arglists :fixed-arities (set (map :fixed-arity arglists)) :max-fixed-arity max-fixed-arity :variadic? (boolean variadic-arglist)})) (defn valid-invoke-arity? "Returns truthy if it is acceptable to invoke `f` with `argc` arguments." [f argc] (or (contains? (:fixed-arities f) argc) (and (:variadic? f) (> argc (:max-fixed-arity f))))) (defn take-when [pred [x & xs :as coll]] (if (pred x) [x xs] [nil coll]))