Skip to content

Instantly share code, notes, and snippets.

Created May 30, 2011 18:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/999256 to your computer and use it in GitHub Desktop.
Save anonymous/999256 to your computer and use it in GitHub Desktop.
clojure defnks macro, an advanced macro like clojure.contrib.def/defnk
(defn is-condition-map? [form]
(and (map? form) (or (:pre form) (:post form))))
(defn contains-keys? [m ks]
(every? #(contains? m %) ks))
(defn- split-args
"(split-args [:a :b :c :opt :d :e :f :opt-def :g 3 :h 4 :i 5])
=> {:opt-def {:g 3, :i 5, :h 4}, :opt [:d :e :f], :obligatory [:a :b :c]}"
[args]
(loop [args args
current :obligatory
result {:obligatory [] :opt [] :opt-def []}]
(if (empty? args)
(assoc
(select-keys result [:obligatory :opt])
:opt-def (apply hash-map (:opt-def result)))
(let [curr (first args)
state (case curr
:opt :opt
:opt-def :opt-def
current)]
(recur
(rest args)
state
(if (not= state current)
result
(update-in result [state] conj curr)))))))
(defmacro defnks
"Warning: Binds \"m\" to the args-map.
ks-args are obligatory keys until a :opt or :opt-def; then optional keys as an
alternating sequence of keywords and defaults values are expected.
Asserts the obligatory keys are there and all keys are obligatory/optional in the arg-map.
Allows a docstring after the fn-name and a condition-map."
[fn-name & more]
(let [f (first more)
docstring (if (string? f) f)
more (if docstring (rest more) more)
ks-args (first more)
body (rest more)
condition-map (if (is-condition-map? (first body)) (first body))
body (if condition-map (rest body) body)
{:keys [obligatory opt opt-def]} (split-args ks-args)
sym-vals (into {} (for [[k v] opt-def]
[(symbol (name k)) v]))
all-ks (vec (concat obligatory opt (keys opt-def)))
ks-as-symbols (map #(symbol (name %)) all-ks)
check-ks-precond `{:pre [(contains-keys? ~'m ~(vec obligatory))
(every? #(some #{%} ~(vec all-ks)) (keys ~'m))]}]
`(defn
~(with-meta fn-name {:doc docstring})
[& {:keys [~@ks-as-symbols] :or ~sym-vals :as ~'m}]
~(merge-with #(vec (concat %1 %2))
check-ks-precond
condition-map)
~@body)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment