Clojure functions with meta-data. Image representation #3

Posted by fmn on October 20, 2009 at 2:13 pm.

Previously on Pixel Shaker : an image is a bounded function that can be coded as a Java-class. This Java-class involves a lots codelines, even in Clojure. Today is an exploration of this concept in a more idiomatic way.

A bounded function is a function with an additional information : its definition domain. Clojure provides an handy way to deals with extra data : the metadata. The metadata is a map of data that can be added using the with-meta function. Let's try to add a definition domain as a metadata :

user> (defn indom? [dom pt]
        (let [[start end] dom
              [x y] pt]
          (and (>= x (first start))
               (<= x (first end))
               (>= y (second start))
               (<= y (second end)))))

'user/indom?

user> (defn f [x y] (+ x y))

'user/f

user> (def dom [[0 0] [4 4]])

'user/dom

user> (def bf (with-meta f {:domain dom})) java.lang.UnsupportedOperationException (NO_SOURCE_FILE:10)

This approach (using with-meta) cannot work as metadata can only work with symbols and collections. After a little googling, i found a alternative way proposed by Philipp Calçado : use proxy to add metadata to Java object. The proxy must extend clojure.lang.IFn and clojure.lang.IObj, implementing meta and withMeta methods. The meta method returns the metadata of the object. The withMeta method returns a new augemented metadata :
user> (def bf
           (let [metadata {:domain dom}]
             (proxy [clojure.lang.IFn clojure.lang.IObj] []
               (withMeta [meta] (merge metadata meta))
               (meta [] metadata)
               (invoke [x y] (if (indom? dom [x y])
                               (f x y)
                               0)))))

'user/bf

user> (bf -1 1) 0 user> (bf 1 2) 3 user> (defn domain [img] (:domain (meta img)))

'user/domain

user> (domain bf) [[0 0] [4 4]] user> (with-meta bf {:a 1}) {:a 1, :domain [[0 0] [4 4]]} user>

Nice! But why be restricted to only include definition domain to functions? Let's abstract one step further and allow to add any meta-data to any function:
user> (defn with-meta-fn [fun metadata]
        (proxy [clojure.lang.IFn clojure.lang.IObj] []
          (withMeta [meta] (with-meta-fn fun (merge metadata meta)))
          (meta [] metadata)
          (invoke [& args] (apply fun args))))

'user/with-meta-fn

The with-meta-fn allows to add any metadata to a function, as with-meta with symbols and collections. This way the construction of a bounded-function (via make-bounded-function) is easier to read.

user> (defn make-bounded-function [dom fun]
        (with-meta-fn (fn [x y] (if (indom? dom [x y])
                                  (fun x y)
                                  0))
          {:domain dom}))

'user/make-bounded-function

user> (def bf (make-bounded-function dom f))

'user/bf

user> (bf 1 1) 2 user> (domain bf) [[0 0] [4 4]]

FMN.

Leave a Reply