Recently we’ve been working on several digital art projects using Field as a development and presentation platform but with Clojure running the core, domain-specific algorithmic code. This choice is, admittedly, partly because Clojure is new and shiny, but we also like the Emacs- and Leiningen-based development environment (complete with continuous integration testing), and Clojure’s clean functional semantics lends itself to realtime, evolutionary artworks. Since Field works at the level of Python-on-Java (via Jython), and Clojure runs in the JVM, the Python and Clojure worlds inevitably collide.
Field hosts Clojure directly – in fact, it hosts several JVM-based languages – using something called Text Transforms. Activate the Clojure transform, as in the splash image above, and execution flips from Python to Clojure and back again. The transform passes Python bindings into the Clojure namespace, and bindings made within Clojure are passed out again (within a dedicated variable called
_clojure). Both Python and Clojure have access to anything the JVM can see via its classpath. There are one or two wrinkles (such as the hack, shown above, of making sure a variable is retrieved from the Python world before its first use in the Clojure world, since Field’s dynamic lookup machinery only works for Python), but everything pretty much just works.
If we’re developing in two languages at once, we need to be able to pass information between them, not least configuration information from Python into Clojure. In Python, we organise our manifest constants without polluting the top-level namespace by declaring things like this:
|1 2 3 4 5 6 7 8 9 10 11 12 13 14 15||
This lets us use names like
5) or (if we use our rather implausible test declaration)
"Hello"). The Python for
Manifest is pretty simple:
|1 2 3 4||
So what of the Clojure world? Passing a
Manifest into Clojure exposes an object constructed from numerous Jython components, whereas what we really want is Clojure hash maps with keywords, so that we can write things like
(:TREE_DEPTH m_Configuration) or
(:C (:A m_Nested)). So, here’s an implementation of something we call
|1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29||
We scan through the keys, turning each one into a Clojure keyword and converting its value (recursively, so that we can handle nested manifest objects and lists). The conversion from Java iterator into Clojure list is rather ugly – I’m a bit surprised that there’s no pre-existing support for this.
Looking back at the Field listing at the top: we pass the various manifest objects as arguments to top-level Clojure functions which are higher-order and hence return the actual functions we call from Python, having hashified the manifests. Here’s the main function from a current artwork:
|1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22||
The big win here is the ability to map-bind the entries of a manifest in one go. Using the
:keys form here binds the names directly, at the cost of a pile of ugly upper-case identifiers in the middle of the code – but there’s no reason why we couldn’t modify
hashify to downcase everything.
The final push: it makes sense to pass composite data objects from Clojure back to Python in the same form (rather straining the original intent of “manifest”), but how do we turn Clojure’s persistent hash maps back into manifest objects? It would be great if we could overload the
Manifest constructor to take a hash map; since we can’t, this is the closest I managed to get: an optional non-keyword argument which can be a hash map.
Manifest now looks like this:
|1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20||
This is effectively “
unhashify” on the Python side (and again it’s recursive to handle nested structures). The instance check for
Map gets us hash maps; the instance check for
List gets us Clojure lists, vectors and sequences. The only real Clojure-dependent part of this is the
.getName() call on the keywords to turn them back into strings. (Again, we’d probably want to upcase these to be typographically correct.)