Hi,

I was thinking about multimethods and the flexibility given by being able 
to dispatch on the value of an arbitrary function of the arguments,
instead of just the class of the first argument as in Java. It seems to me 
that we could be even more flexible if each method implementation
was associated with a predicate function of the args, instead of being 
associated with a fixed dispatch value. See the below code for an example 
of what I mean:

(ns polymorphism
  (:use clojure.test))

(def pm-to-dispatch-pred-map (ref {}))

; these are kind of like multimethods, so let's give them a similar name - 
polymethods.
(defmacro defpoly [a-name [& args]]
  "define a polymethod signature"
  `(do
     (defn ~a-name [~@args]
         (let [dispatch-pred-to-impl-fn-map# (get 
@polymorphism/pm-to-dispatch-pred-map ~a-name)
               matching-keys# (filter #(apply % [~@args]) (keys 
dispatch-pred-to-impl-fn-map#))
               num-matches# (count matching-keys#)]
           (case num-matches#
             0 (throw (Exception. (str "No matching function defined for 
polymethod " ~a-name " and args " [~@args])))
             1 (apply (get dispatch-pred-to-impl-fn-map# (first 
matching-keys#)) [~@args])
             (throw (Exception. (str num-matches# " matching functions 
defined for polymethod " ~a-name " and args " [~@args]))))))
       (dosync (alter polymorphism/pm-to-dispatch-pred-map assoc ~a-name 
{}))))

(defmacro defpolyimpl [a-name dispatch-pred & body]
  "define a polymethod implementation, for a given dispatch predicate"
  `(let [dispatch-pred-to-impl-fn-map# (get 
@polymorphism/pm-to-dispatch-pred-map ~a-name)]
     (if (nil? dispatch-pred-to-impl-fn-map#)
       (throw (Exception. "No such polymethod: " ~a-name)))
     (let [impl-fn# (fn ~@body)]
       (dosync (alter polymorphism/pm-to-dispatch-pred-map assoc ~a-name 
(assoc dispatch-pred-to-impl-fn-map# ~dispatch-pred impl-fn#)))
       ~a-name)))


(deftest polymorphism-test
  (defpoly find-name [a-map])
    
  (defpolyimpl find-name
    #(and (contains? % :first-name) (contains? % :surname))
    [a-map]
    (str (:first-name a-map) " "  (:surname a-map)))

  (defpolyimpl find-name
    #(contains? % :full-name)
    [a-map]
    (:full-name a-map))

  (def personA {:first-name "John" :surname "Smith"})
  (def personB {:full-name "Jane Bloggs"})
  (is (= "John Smith" (find-name personA)))
  (is (= "Jane Bloggs" (find-name personB))))

(run-tests)

I think this system of "dispatch predicates" instead of dispatch values is 
more flexible/expressive, because you can easily emulate multimethods: if 
your multimethod had dispatch function dispatch-fn and a dispatch value 
dispatch-val, you would use
  #(isa? (apply dispatch-fn %&) dispatch-val)
as the dispatch predicate. Whereas I can't see a simple way to express an 
arbitrary set of "polymethods" in terms of multimethods.

There are a few problems I can see with this approach, but none of them 
seem to be dealbreakers to me:
- You need to filter a list to determine the appropriate function 
implementation, whereas multimethods just look up the dispatch value in a 
hash map. This could decrease performance if there are many implementations.
- In this version, they don't compose well: somebody could define a new 
implementation somewhere else which breaks your code, because two dispatch 
predicates match the args you're using. We could get around this with 
something like prefer-method, or by remembering the order in which 
implementations are defined, and always taking the first/last one which 
matches the args.
- Maybe this shouldn't be called polymorphism, since there is no notion of 
type involved anywhere, except for the "types" defined by the predicates.

Is there anything wrong with this approach, or any particular reason it 
wasn't done this way in Clojure?

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