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