On Clojure

August 26, 2010

Reusable method implementations for deftypes

Filed under: Libraries — khinsen @ 2:55 pm

One of the big new features in the recently released Clojure 1.2 is the possibility of defining new types having data field and implementing methods conforming to interfaces. Clojure provides two levels of user-defined types: the basic deftype, for defining everything from scratch, and defrecord, which adds method implementations for a couple of interfaces (some from Java, some from Clojure) that make the new type act like a Clojure map object.

But what if you want something in between? For example, to make your type a good Clojure citizen, you want it to accept metadata (a feature provided by defrecord), but you don’t want all the map stuff. Or perhaps you want a map-like interface for the fields of your type, but without the possibility to extend the map with new keys. Clojure doesn’t help you out of the box; your only choice is to re-implement the required interfaces yourself, or borrow the code from Clojure’s defrecord, if you are up to deciphering how it works. There is no way to reuse method implementations.

This also becomes a problem if you want to reuse your own method implementations. You’d need to write your methods outside of any deftype, possibly in a way that allows parametrization, and then insert the code into a deftype form. You might be tempted to use macros for this, but that won’t work: macros are expanded as part of the evaluation of forms, but inside a deftype form, almost nothing gets evaluated. The only place where macros can be put to use inside a deftype is inside the code of the individual methods.

The library methods-a-la-carte (also available at clojars) comes to your rescue. It defines a templating system, similar in spirit to syntax-quote but with some important differences, that lets you define parametrized templates for methods and sets of methods. It also defines an enhanded version of deftype, called deftype+, which expands such templates inside its body. Finally, it comes with a small collection of predefined method implementations, corresponding to the features of defrecord but available individually.

First, a simple example of a type that reuses just the metadata protocol implementation:

(ns example
  (:use [methods-a-la-carte.core :only (deftype+)])
  (:use [methods-a-la-carte.implementations :only (metadata keyword-lookup)]))

(deftype+ foo
  [field1 field2 __meta]
  ~@(metadata __meta))

(def a-foo (with-meta (new foo 1 2) {:a :b}))
(prn (meta a-foo))

This type has two plain fields (named rather unimaginatively field1 and field2), and a special field __meta for storing the metadata. This happens to be the name that Clojure’s defrecord uses for the metadata field, but this is unimportant. What is important is that the name begins with a double underscore, as deftype handles such fields specially: they are omitted from the constructor argument list (to the best of my knowledge this is an undocumented feature of deftype). Whatever name you choose, you have to give the same name as a parameter to the metadata template.

Let’s add another feature to our type: keyword lookup:

(deftype+ foo
  [field1 field2 __meta]
  ~@(metadata __meta)
  ~@(keyword-lookup field1 field2))

(def a-foo (new foo 1 2))
(prn (:field1 a-foo))
(prn (:field2 a-foo))

The parameters to the template keyword-lookup are the field names for which you want keyword lookup. It can be any subset of the type’s fields.

By now you might be curious to know how the templates are defined, for example in order to define your own. Here’s the metadata template, the simplest one in the collection:

(defimpl metadata [fld]
  clojure.lang.IObj
  (meta [this#]
    ~fld)
  (withMeta [this# m#]
    (new ~this-type ~@(replace {'~fld 'm#} '~this-fields))))

This template has one parameter, fld, naming the field that stores the metadata. Everything after the parameter list is the content of the template, with a tilde standing for expressions that are replaced by their values, just as with syntax-quote templates. Another similarity with syntax-quote is that symbols ending with # are replaced by freshly generated unique symbols.

There are two major differences between the new templating mechanism and the well-known syntax-quote:

  1. Symbols are not namespace-resolved. This is important because, contrary to the use of templates in macro definition, namespace resolution is not appropriate for most of the symbols in a method template (method names, method arguments, interface and protocol names).
  2. Symbols are not looked up in the lexical environment (there is none), but first in a dynamic environment and then in the namespace of the template definition.

The dynamic environment is initialized by deftype+ with the following values:

  • this-type: the symbol naming the type being defined
  • this-fields: the vector of field names supplied to deftype+

The above method template used both these values in its code for withMeta. Here is what the first example (type foo with just the metadata implementation) expands to:

(deftype
  foo
  [field1 field2 __meta]
  clojure.lang.IObj
  (meta [this#2515] __meta)
  (withMeta [this#2515 m#2516] (new foo field1 field2 m#2516)))

As with all templating mechanism, including syntax-quote, the interplay of evaluation rules, substitution rules, and quoting requires some experience before it becomes to seem natural. Be prepared for some head-scratching as you write your first templates. Simply using them should be much easier, and probably sufficient for most users. Feedback welcome!

About these ads

2 Comments »

  1. Thank you for the post. Your blogging platform converts open bracket colon “o” ([:o) to an emoticon, which impedes reading some of your code snippets.

    Comment by billsmithaustin — February 26, 2013 @ 5:15 pm

    • I know, and I agree. But it seems the only way to disable emoticon conversion is through a global setting of the blog. Since I don’t own this blog, I can’t do that.

      Comment by khinsen — February 26, 2013 @ 5:36 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: