Single Abstract Method Macro

John Rose’s article, Scheme in One Class, introduced me to the notion of Single Abstract Method, or SAM, classes. One of the proposed APIs for JSR-292 allows a MethodHandle (the Java version of a closure) to be cast to any SAM class.

In Java, a SAM can be either an interface or a class, but if it’s a class then it’s usually abstract. The interface or class has exactly one method. Callbacks are often specified as SAM interfaces. The standard Java library has lots of SAM interfaces, such as Runnable and ActionListener.

In Clojure, it’s easy to interoperate with these Java interfaces with reify (or proxy for abstract classes) but it’s tiresome to type out. Since reflection can give us the name of the method, why not let the compiler do the work for us?

(defmacro single-method
  "Returns a proxied or reified instance of c, which must be a class
  or interface with exactly one method. Forwards method calls to the
  function f, which must accept the same number of arguments as the
  method, not including the 'this' argument.

  If c is a class, not an interface, then it must have a public,
  no-argument constructor."
  [c f]
  {:pre [(symbol? c)]}
  (let [klass (resolve c)]
    (assert (instance? java.lang.Class klass))
    (let [methods (.getDeclaredMethods klass)]
      (assert (= 1 (count methods)))
      (let [method (first methods)
            method-name (symbol (.getName method))
            arg-count (count (.getParameterTypes method))
            args (repeatedly arg-count gensym)]
        (if (.isInterface klass)
          `(let [f# ~f]
             (reify ~c (~method-name ~(vec (cons (gensym "this") args))
                                     (f# ~@args))))
          `(let [f# ~f]
             (proxy [~c] [] (~method-name ~(vec args) (f# ~@args)))))))))