On Wed, Dec 22, 2010 at 11:36 PM, Sunil S Nandihalli
<sunil.nandiha...@gmail.com> wrote:
> the caveat is that the order in which you specify the methods would matter
> .. since that is the order in which it is going to check for the appropriate
> method to call.. Just like condp again.

That seems sucky. What about adding a priority parameter to your
defmethod-analogue? The predicates are kept sorted by priority. So

(my-defmulti foo)

(my-defmethod foo 4
  (constantly true)
  [x y] (str "x = " x "\ny = " y "\n"))

(my-defmethod foo 3
  #(= %2 1)
  [x y] (str "y is one!\nx = " x))

(my-defmethod foo 2
  #(= %1 %2)
  [x y] (str "x = y = " x))

(my-defmethod foo 1
  #(or (> %1 5) (> %2 5) (> 1 %1) (> 1 %2))
  [x y] (str "One is out of range!\n x = " x "\ny = " y "\n"))

would check for out of range first, then for equality, then for the
default case, despite these being defined in the reverse of that
order.

Of course this could be nicened up further. For example, extend the
scope of the bindings to cover the predicate as well as what follows;
and if the object after the bindings is not a list, if it's the value
true treat it as (constantly true) and if it's a vector check it for
equality against the arguments, treating any vector element equal to
the clojure.core multiplication function as matching anything (so,
under normal circumstances, a literal * in the vector is a wild-card):

(my-defmethod foo 4 [x y]
  true
  (str "x = " x "\ny = " y "\n"))

(my-defmethod foo 3 [x y]
  [* 1]
  (str "y is one!\nx = " x))

(my-defmethod foo 2 [x y]
  (= x y)
  (str "x = y = " x))

(my-defmethod foo 1 [x y]
  (or (> x 5) (> y 5) (> 1 x) (> 1 y))
  (str "One is out of range!\n x = " x "\ny = " y "\n"))

An untested implementation:

(def qlzqqlzuup (Object.))

(defmacro my-defmulti [name]
  `(def name
     (let [dtable (atom (sorted-map))]
       (fn [& args]
         (if (= (first args) qlzqqlzuup)
           (let [[_ pri predmeth] args]
             (swap! dtable assoc pri predmeth))
           (loop [d (seq @dtable)]
             (if d
               (let [[_ [pred meth]] (first d)]
                 (if (apply pred args)
                   (apply meth args)
                   (recur (next d))))
               (throw IllegalArgumentException
                 (str "no matching method in " name " for " args)))))))))

(defn wildmatch [matcher & args]
  (loop [m (seq matcher) a (seq args)]
    (if (and m a)
      (let [fm (first m)]
        (if (or (= fm *) (= fm (first a)))
          (recur (next m) (next a))))
      true)))

(defmacro my-defmethod [multifn priority bindings matcher & body]
  (let [pred (cond
               (= true matcher) `(constantly true)
               (vector? matcher) `(fn ~bindings
                                    (wildmatch ~matcher ~...@bindings))
               true `(fn ~bindings ~matcher))]
    `(~multifn qlzqqlzuup ~priority [~pred (fn ~bindings ~...@body)])))

WARNING: Untested!

IF it works, this implementation has three particular properties not
mentioned above:

1. Defining two methods of the same multifn with the same priority
   isn't possible -- the later one will replace the earlier method of
   the same priority.

2. Vector matching matches if there are fewer arguments than the
   vector length, and the n arguments match against the first n
   elements of the vector, and if there are more arguments than the
   vector length, and the n vector elements match against the first
   n arguments, as well as if there are equal numbers of each and they
   match in sequence.

3. If no method matches it throws an IAE.

Note also that if a method is given a vector as matcher, and that
vector contains expressions, those expressions get run every time the
matcher does (including in some cases where it doesn't match!); e.g.

(my-defmethod foo 3 [x y]
  [(* bar quux) 42]
  (str "x is bar times quux and y is 42"))

will generate a predicate test defined as

(fn [x y] (wildmatch [(* bar quux) 42] x y))

and the lookup and multiplication of bar and quux will be done every
time this is called. Obviously it's best if no such computation is
expensive (unless it needs to be and can't be hoisted out of the
defmethod because it must be run every time) or has side effects
(other than temporary debugging printlns, anyway).

As you can see also, the bindings are in effect where the (* bar quux)
appeared, so the above equality test method could also have been
written as:

(my-defmethod foo 2 [x y]
  [y]
  (str "x = y = " x))

which yields a predicate defined by

(fn [x y] (wildmatch [y] x y))

which in turn will simply see if y is equal to x.

That makes some cases like that one nicer to write, but also means
that the bindings will shadow globals of the same name in the vector,
which might be a surprise some of the time; if we had a global y and
expected the above to check x against that and not against the
argument y we'd get a nasty, and perhaps subtle, bug.

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