Digital Magpie

Ooh, ooh, look - shiny things!

Embedding Clojure (Part 2)

Following on from the last post, it actually turns out to be much easier to do most of the work in Clojure itself — no need for all of that tiresome messing around with Vars and Symbolss on the Java side of things! The trick is to define an abstract class in Java to set a few things up and use as a hook, than implement this in Clojure. I’ll go through both sides of this, starting with the Java stuff.

The Abstract Class in Java

Basically, I’m using the Java side of things to set up my text pane with a standard stylesheet (I’d like it to use a proportional font, with different colours for input, output, and error text) and to install a key listener to send commands to the Clojure repl whenever the user hits enter or return. The basic class then, is

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class InteractiveConsole {
    private final Document _document = createStyledDocument():
    private final JTextPane _textpane;
    private final PipedWriter _inWriter = new PipedWriter();
    private final Reader _in;
    private final Writer _out = new PrintWriter(new DocWriter("output"), true);
    private final Writer _err = new PrintWriter(new DocWriter("error"), true);
    protected InteractiveConsole(JTextPane textpane) throws IOException {
        _textpane = textpane;
        _in = new PipedReader(_inWriter);
    }
}

The createStyledDocument method, which I won’t include here, just sets up the style context and my colour scheme. The DocWriter class that is references is an trivial writer subclass that just calls insertString on the document with the named style. The other class that I’ll be wanting to use is a runnable so that I can launch the Clojure REPL on it’s own thread. It’s about as trivial as it gets, it just calls back into the two abstract methods that I’m going to provide to provide my Clojure code somewhere to hook onto.

1
2
3
4
5
6
7
8
9
10
11
12
13
private class ConsoleRunner implements Runnable {
    private final Map<String, Object> _context;
    public ConsoleRunner(Map<String, Object> context) {
      _context = context;
    }
    @Override
    public void run() {
        for (Map.Entry<String, Object> var : _context.entrySet()) {
            bindVariable(var.getKey(), var.getValue());
        }
        doStart(_in, _out, _err);
    }
}

With this I can provide a start method that installs my key listener and then launches a new thread with an instance of this runnable. The two hook methods that I’m providing are:

  • abstract void bindVariable(String,Object) to allow me to set up some domain objects on the clojure side of things; and

  • abstract void doStart(Reader,Writer,Writer) to actually start the REPL, using the provided input, output, and error streams.

The Clojure Implementation

Turns out to be trivial as well, the implementation of bindVariable just interns the object passed in into the user namespace, it’s a one liner in Clojure.

1
2
(defn -bindVariable [this name value]
    (intern 'user (symbol name) value))

The doStart method isn’t much more involved either, it just sets up the bindings and then launches the REPL.

1
2
3
4
5
(defn -doStart [this #^Reader in #^Writer out #^Writer err]
    (binding [*in*  (LineNumberingPushbackReader. in)
              *out* out
              *err* err]
        (clojure.main/repl)))

Notice that here I have added type annotations so that the correct method gets implemented, without these the Clojure code compiled but then I got abstract method errors at runtime. Check out the docstring for the repl function as well, there are a few useful options (for example in my actual code I have an :init function to switch to the user namespace, and a custom prompt). For completeness, here’s the rest of the Clojure file with the code required to inherit from the Java base class.

1
2
3
4
5
6
7
8
9
10
11
(ns com.example.ClojureConsole
    (:import (clojure.lang LineNumberingPushbackReader)
             (java.io Reader Writer)
             (java.util Map))
    (:require (clojure main))
    (:gen-class
     :extends bg.beer.editor.InteractiveConsole
     :init init
     :constructors {[javax.swing.JTextPane] [String javax.swing.JTextPane]}))
(defn -init [textpane]
    [[textpane]])

Summary

This approach has the advantage that any additional configuration can happen in the clojure code. It would also be easy, for example, to have an additional script that was always run at start up, to allow the user to customize the console further (similar to the .emacs file in Emacs). You could also move most of the work that I’m doing in Java into the Clojure code. I haven’t done this as I may want to support multiple languages in my console and it’s nice to have a common stylesheet and keybindings (e.g. for history) across languages. Your mileage may vary.

Embedding Clojure in an Existing Application

I’ve been taking a look at Clojure lately, as a JVM friendly flavour of Lisp I’ve got to say it looks pretty interesting. One problem that I’ve had though is that all of the documentation out there (of which there’s very little, to be honest) seems to assume that you’ll be writing/running a pure Clojure program as you main application. There’s plenty of information about calling Java code from Clojure programs, and some information about extending Java classes and interfaces with Clojure code, but nothing about getting the two talking together at runtime. So here’s how it’s done.

First off you need to set up the symbols and namespaces that you are going to need to start up a clojure environment.

1
2
3
4
5
6
Symbol main = Symbol.create("main");
Symbol clojureMain = Symbol.create("clojure.main");
Symbol user = Symbol.create("user");
Symbol require = Symbol.create("require");
Namespace userNS = Namespace.findOrCreate(user);
Namespace clojureMainNS = Namespace.findOrCreate(clojureMain);

Once you have these you can require the clojure main namespace, this is the same one that is used to run scripts or start a REPL from the comand line.

1
Var.intern(RT.CLOJURE_NS, require).invoke(clojureMain);

Then, and this is the bit that I had trouble working out, you need to bind your application’s domain model (or at least those bits of it that you want to expose) into the user namespace in Clojure.

1
2
3
4
5
for (Map.Entry<String, Object> global : globals.entrySet()) {
    String key = global.getKey();
    Object value = global.getValue();
    Var.intern(userNS, Symbol.create(key), value);
}

Finally you’re ready to grab the main method and run it.

1
Var.intern(clojureMainNS, main).applyTo(RT.seq(new String[0]));

The emtpy string array here is emulating an emty sargument list at the command line. Some things to note here: you need to intern vars before you can use them, even for core library features like require, this surprised me at first but when you remember that most Lisps are built around a very small core of special forms with everything else defined in Lisp, it makes some sense.

Guice Development Stages

Guice has a concept called develpment stages which affect how the library works. Guice is written with server-side applications in mind (it is from Google, after all) so the behaviour for production mode is to do a bunch of work up front so that it can catch errors as soon as possible.

The development mode defers work (i.e. object creation) until the last moment; Here’s how the Javadoc notes describe them:

  • Development we want fast startup times at the expense of runtime performance and some up front error checking.

  • Production we want to catch errors as early as possible and take performance hits up front.

  • Tool we’re running in a tool (an IDE plugin for example).

Ignoring tool mode, this is exactly the opposite of the behaviour that you want to see for client-side development. For client applications you want to have an much error checking as possible during development but for production use (i.e. use by a real user) you want to go for faster start-up times.

Note that for production use it is also advantageous to defer error checking until later as well; after all, there is no point in refusing to start an app because feature ‘foo’ isn’t working, if the user doesn’t use or care about ‘foo’!