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

Reply via email to