A couple of days ago, I finally had enough of manually extracting function keyword arguments. defnk is cool and all, but it does nothing for fn, letfn, defmethod, or any other form with a parameter list. Map destructuring is also cool, but providing defaults requires writing each symbol twice, once in :keys and once in :or.
The macro I ended up with, fn-keywords, owes much to Common Lisp's lambda lists. Instead of an outer wrapper for Clojure's various function forms, it provides a kind of inner wrapper, really a let form specialized for keyword arguments. Each keyword is defined in a keyword spec, which allows for an optional default value, and for an optional symbol to be bound to true if the keyword was actually provided in the function call. Note that keywords arguments may be & rest in style, or provided as either maps or vectors. This should cover all useful cases for keyword arguments, including their use in functions with multiple arities. Example: (defn test1 [req1 req2 & kw-args] (fn-keywords [:kw1 [:kw2 "kw2 default"] [:kw3 "kw3 default" kw3-supplied?]] kw-args ;; function body {:req1 req1 :req2 req2 :kw1 kw1 :kw2 kw2 :kw3 kw3 :kw3-supplied? kw3-supplied?})) > (test1 1 2 :kw3 "there") {:req1 1, :req2 2, :kw1 nil, :kw2 "kw2 default", :kw3 "there", :kw3-supplied? true} > (test1 1 2 :kw1 "there") {:req1 1, :req2 2, :kw1 "there", :kw2 "kw2 default", :kw3 "kw3 default", :kw3-supplied? false} > (test1 1 2 :kw1 "hello" :kw2 "there") {:req1 1, :req2 2, :kw1 "hello", :kw2 "there", :kw3 "kw3 default", :kw3-supplied? false} Macro code: (defmacro fn-keywords "Adds flexible keyword handling to any form which has a parameter list: fn, defn, defmethod, letfn, and others. Keywords may be passed to the surrounding form as & rest arguments, lists, or maps. Lists or maps must be used for functions with multiple arities if more than one arity has keyword parameters. Keywords are bound inside fn-keywords as symbols, with default values either specified in the keyword spec or nil. Keyword specs may consist of just the bare keyword, which defaults to nil, or may have the general form [:keyword-name keyword-default-value* keyword-bound?*]. keyword- bound? is an optional symbol bound to true if the keyword was supplied, and to false otherwise." [kw-spec-raw kw-args & body] (let [kw-spec (map #(if (sequential? %) % [%]) kw-spec-raw) keywords (map first kw-spec) symbols (map (comp symbol name) keywords) defaults (map second kw-spec) destrmap {:keys (vec symbols) :or (apply hash-map (interleave symbols defaults))} supplied (reduce (fn [m [k v]] (assoc m k v)) (sorted-map) (remove (fn [[_ val]] (nil? val)) (partition 2 (interleave keywords (map (comp second rest) kw- spec))))) kw-args-map (gensym)] `(let [kw-args# ~kw-args ~kw-args-map (if (map? kw-args#) kw-args# (apply hash-map kw-args#)) ~destrmap ~kw-args-map] ~@(if (empty? supplied) body `((apply (fn [~@(vals supplied)] ~...@body) (map (fn [x#] (contains? ~kw-args-map x#)) [~@(keys supplied)]))))))) More examples: (defmulti test2 (fn [x & rest] (class x))) (defmethod test2 java.lang.Integer [req & kw-args] (fn-keywords [[:kw1 "default" kw1-supplied?]] kw-args {:method "integer" :req req :kw1 kw1 :kw1-supplied? kw1-supplied?})) (defmethod test2 java.lang.String [req & kw-args] (fn-keywords [[:kw1 "default"]] kw-args {:method "string" :req req :kw1 kw1})) (defn test3 ([req1 kw-args] (fn-keywords [:kw1 :kw2] kw-args {:req1 req1 :kw1 kw1 :kw2 kw2})) ([req1 req2 kw-args] (fn-keywords [:kw1 :kw2] kw-args {:req1 req1 :req2 req2 :kw1 kw1 :kw2 kw2}))) (defn test4 [] (letfn [(inner [& kw-args] (fn-keywords [:kw1 :kw2] kw-args {:kw1 kw1 :kw2 kw2}))] (inner :kw1 "hello" :kw2 "world"))) Credit where it's due: I lifted ideas and bits of code from defnk. Thanks, Meikel! Comments welcome. -- Constantine Vetoshev -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en