On Sat, Dec 11, 2010 at 3:32 AM, Alexander Yakushev <yakushev.a...@gmail.com> wrote: > I am currently giving some lectures about Clojure to a group of > students. One of the Lisp features I promote to them is the ability to > write language in the language itself. So during the lecture when I > talked about multimethods one student asked if one could write own > multimethods implementation if Clojure didn't have them. I went all > like "Sure, you just..." and stuck. My first thought was to save > multimethod into an atom and the list of methods into metadata of that > atom, but as I found out atoms cannot hold metadata. > Desperately I looked into Clojure sources for implementation of > multimethods and saw that it is done using the MultiFn Java object. I > guess this is done for performance and there is still a way do this in > pure Clojure (I mean, in pure Lisp).
As a first stab, I'd use an atom holding a list of dispatch-fn, map-of-argfn-return-values-to-methods, default-method Something like: (defmacro defmethod [name dfn] `(def ~name (atom [~dfn {} (fn [& args#] (throw (IllegalArgumentException. (str "no dispatch for " args#))))])) (defn add-method [name dfn-val meth] (swap! name assoc-in [1 dfn-val] meth)) (defmacro addmethod [name dfn-val bindings & body] `(add-method ~name ~dfn-val (fn ~bindings ~...@body))) (defn set-method-default-meth [name meth] (swap! name assoc 2 meth)) (defmacro setmethod-defaultmeth [name bindings & body] `(set-method-default-meth ~name (fn ~bindings ~...@body))) (defn call-method [name & args] (let [a @name] (apply (get-in a [1 (apply (a 0) args)] (a 2)) args))) This doesn't bind the method name itself to a callable function; you have to (call-method name args ...) rather than (name args ...). It's also untested. But it should give you some idea of how something like this can be implemented. The default method is called if the dispatch function's return value isn't found in the map. The default default method is the IAE throw in the first macro. Methods can be replaced by doing a fresh addmethod with the same dispatch value, but I didn't bother to include a deleter. It should be simple enough to implement with dissoc. (It's a shame there isn't a dissoc-in. Oh, wait, you can easily write your own: (defn dissoc-in "Removes an entry in a nested associative structure. (= (dissoc-in {:a {:b 1 :c 2} :d {:e 3 :f 4}} [:a :c]) {:a {:b 1} :d {:e 3 :f 4}})" ([m keys] (if (= 1 (count keys)) (dissoc m (first keys)) (let [ks (butlast keys) k (last keys)] (assoc-in m ks (dissoc (get-in m ks) k)))))) (this one IS tested).) Making the thing work with (name args ...) is not too too difficult. You'd have to have defmethod output both a def of an atom like above, but with a gensym for a name, and a defn with the specified name that has the body of call-method, more or less, but with the name arg fixed to the gensym. There'd also need to be a global names-to-gensyms table somewhere to make addmethod and the like work. This variation looks like (defmacro defmethod [name dispatch-fn] `(do (def ...) (defn ~name ...))). Another alternative is for defmethod to def the name to a function that closes over the atom and, via special sentinel arguments, implements all the functionality of addmethod etc. as well as call-method. When called without sentinel arguments it does a normal call-method; when called as (name :add dispatch-val meth) it adds a method; etc. This variation looks like (defmacro defmethod [name dispatch-fn] `(def ~name (let [a (atom ...)] (fn ...)))). -- 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