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
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.
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.
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.
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.
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.
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.
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 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’!