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

Reply via email to