On Tue, Dec 23, 2008 at 10:15 AM, Adam Harrison (Clojure)
<[email protected]> wrote:
>
> I have been trying to work out how best to validate the structure of
> arguments passed into macros. It would appear that there are several
> ways of tackling this and I was wondering which is considered best
> practice. As an example, lets say I want a macro, 'print-even' which
> prints its arguments if they are even in number and throws an exception
> otherwise (sorry for the poor example but it's illustrative). I can see
> at least three implementations:
>
> 1) Defer all the work until the expanded macro code is executed:
>
> (defmacro print-even [& args]
>  `(if (even? (count '~args))
>    (print ~...@args)
>    (throw (Exception. "Uneven"))))
>
> 2) Perform the even? check at macro time, but leave throwing the
> exception until runtime:
>
> (defmacro print-even [& args]
>  (if (even? (count args))
>    `(print ~...@args)
>    `(throw (Exception. "Uneven"))))
>
> 3) Throw the exception at macro time:
>
> (defmacro print-even [& args]
>  (if (even? (count args))
>    `(print ~...@args)
>    (throw (Exception. (format "Uneven (%d arguments)" (count args)))))
>
>
> Intuitively (3) feels like the right choice for two reasons:
>
> - It does as much of the work as possible up front
> - Given that defmacro is effectively extending the syntax of the
> language, you could view calling print-even with an odd number of
> arguments as a syntax error which seems best raised as early as possible

This was my intuitive guess as well. However, looking at condp, it
seems that Rich prefers (at least in that case) option 2 (note the (=
0 n) clause in the cond expression):

(defmacro condp
  [pred expr & clauses]
  (let [gpred (gensym "pred__")
        gexpr (gensym "expr__")
        emit (fn emit [pred expr args]
               (let [[[a b c :as clause] more]
                       (split-at (if (= :>> (second args)) 3 2) args)
                       n (count clause)]
                 (cond
                  (= 0 n) `(throw (IllegalArgumentException. "No
matching clause"))
                  (= 1 n) a
                  (= 2 n) `(if (~pred ~a ~expr)
                             ~b
                             ~(emit pred expr more))
                  :else `(if-let [p# (~pred ~a ~expr)]
                           (~c p#)
                           ~(emit pred expr more)))))
        gres (gensym "res__")]
    `(let [~gpred ~pred
           ~gexpr ~expr]
       ~(emit gpred gexpr clauses))))

> This has an obvious flaw though - the caller may expect to be able to
> supply an expression which they want evaluated:
>
> (print-even (range 1 3))
>
> With definition (3) this throws an 'Uneven' exception because we're
> counting the length of the unevaluated argument list.

Well, isn't this still one argument, a seq? There may very well be
flaws with (3) regarding evaluation, but I'm not sure this example
exposes them.

- J.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to