On Sat, Jan 29, 2011 at 5:18 PM, Robert McIntyre <r...@mit.edu> wrote:
> I see, so you wanted to allow some subset of the optional arguments.
>
> Might I recommend the following?
> Sets are already functions that check for inclusion of the objects on
> which you call them, so instead of (contains? some-set ele) just
> (some-set ele) will work.  Also, upon actually making a typo you just
> get the statement that (contains? #{:a :b} each) has failed, while I
> think it would be nicer if it actually showed the offending misspelled
> keyword. Unfortunately implementing this proved harder than I expected
> and the best I could come up with was:
>
> (defn hello
>  [& {:keys [a b] :as input}]
>  (dorun (map #(eval `(assert (#{:a :b} (quote ~%))))
>              (keys input)))
>  "hello")
>
> I'd be curious what the right way to to this is.

A macro.

(defmacro defkfn [name keys & body]
  (let [[docstring keys body]
        (if (string? keys)
          [[keys] (first body) (rest body)]
          [[] keys body])]
    `(defn ~name ~@docstring [& {:keys ~keys :as input#}]
       (doseq [k# (keys input#)]
         (assert (~(set (map (comp keyword str) keys)) k#)))
       ~@body)))

user=> (defkfn foo "docstring" [a b] (+ a (* 2 b)))
#'user/foo
user=> (doc foo)
-------------------------
user/foo
([& {:as input__4130__auto__, :keys [a b]}])
  docstring
nil
user=> (foo :a 1 :b 2)
5
user=> (foo :a 2 :b 1)
4
user=> (foo :a 2 :b 1 :c 3)
#<CompilerException java.lang.AssertionError: Assert failed: (#{:a :b}
k__4131__auto__) (NO_SOURCE_FILE:501)>

Docstring optional. Sorry about the ugly argument list in the doc
output. That can't be easily changed without adding code to the macro
to fiddle with the var's meta. :) This suffices to show how to make
this sort of thing work, though. Note that it basically just automates
outputting code like yours, Robert, minus the eval which can be
dispensed with. (doseq [k (keys input)] (assert ({:a :b} k))) works
fine; there's nothing magical about assert that stops you using it in
such a manner.

Modifying this to allow supplying optional default values for the keys
is left as an exercise for the reader. I suggest a syntax like {:keys
a-key [another-key default-value-expr] more-keys}. The macro will need
to parse this in the let part and build a map of keywords to default
values, and avoid multiply evaluating default-value-exprs. The actual
output part of the macro, the syntax-quoted expression, will need
changing as well to merge the defaults with the input. I'd suggest the
output should probably look like this:

(let [default-map__1082_auto] {:another-key default-value})
  (defn foo [arg-map__1083_auto]
    (let [{:keys [keys...] :as input__1084_auto}
           (merge default-map__1082_auto arg-map__1083_auto)]
      (doseq [k__1085_auto input__1084_auto]
        (assert ...))
      body...)))

-- 
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