Really cool ! Some ideas which may (or may not ?) enhance it even further :
Since it creates new local bindings, maybe make it look more like other binding forms : instead of (fn-keyword [kw spec] init-kw-val body) : (let-keywords [ [kw spec] init-kw-val ] body ) ? or (let-kw) for short ? Indeed the name (fn-keyword) suggests to me that the expansion will generate some (fn ...) structure. Overall, pretty cool, thanks for sharing ! This deserves to be placed in contrib, in my opinion. Cheers, -- Laurent 2009/11/16 Constantine Vetoshev <gepar...@gmail.com>: > 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 -- 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