NOTE: Updated 2008-12-12 to reflect Clojure's new syntax.
Imagine Qt4 bindings for Lisp that are:
- Officially supported
- Thoroughly documented, with tons of examples
- Consistently up-to-date
Keep dreaming, right? Your first thought might be to check CLiki for some Common Lisp bindings. There is indeed a Qt project there. Except at a glance, it's listed as working only for CMUCL. And the last update was 2003. And the download link is 404'd. Oops!
But there's a link to a some QT4 bindings, CFFI style. Much more promising. Except... last update: March 2007. "Project status: dead." Never mind. There's also this and maybe a few others you could Google up, but I haven't tried them and I'm not going to.
Kudos to the people who wrote the above; providing bindings for all of Qt4 is a daunting task and it's no wonder it's not done for Common Lisp. Doesn't change the reality of the fact that Qt4 (like many other libraries) is a no-go in Common Lisp. There's nothing stopping Common Lisp from having great libraries except lack of manpower; but lack of manpower and lack of mature, tested, up-to-date libraries is still a real problem when you want to write an application today.
Luckily, it turns out there ARE Qt4 bindings that you can use from Lisp. That Lisp is Clojure, and the bindings are Qt Jambi. Many people are excited about Clojure nowadays, and this is one big reason (of many). It's hard to beat Java when it comes to libraries, and Clojure gets them all for free.
Let's try to write one of the official Qt4 examples / tutorials in Clojure and see how it goes. Keep in mind that I'm still learning Clojure, so this may be far from ideal, but it should give you a bit of a taste of Clojure and Qt Jambi if you've never seen it before.
To spoil the ending a bit: It works! In Vista and Linux (I don't have an OS X box to try, but it'll probably work there too):
To set up Clojure, follow the directions on the Clojure wiki. Be sure to get the SVN version of Clojure, because it's being developed very rapidly and is often updated many times per week. You can also check out some recent posts on Bill Clementson's blog, and this movie on LispCast which walks through setting up Clojure and Emacs/SLIME.
To make Qt Jambi available to Clojure, you may have some options. If you use Linux, your distro may make this available via your package manager. Otherwise go to the Qt download site and download "Qt for Java" for your OS. This is a freaking huge download, but it includes all the documentation and source code and plugins; all you need out of it are a couple of JAR files. There are two JARs you need, one cross-OS and one specific to your OS.
You have to put these JARs on your CLASSPATH so Clojure can find them. There are lots of ways to do this; one way is on the commandline:
java -cp qt-jambi-4.4.3_01.jar:qtjambi-linux32-gcc-4.4.3_01.jar:clojure-lang-1.0-SNAPSHOT.jar clojure.lang.Repl
add-classpath in Clojure, which lets you edit the classpath while Clojure is running. I was unable to get this to work with Qt Jambi, but I didn't try very hard. In reality I just use a shell script so I can dump a bunch of JARs into a directory, and they will all be added to CLASSPATH automatically when I start Clojure.
That's it. Installing libraries in Clojure couldn't be much easier.
Here's the complete example code for download. Let's walk through it a bit. First we have to import the Qt Jambi apps into Clojure. Qt Jambi is on our CLASSPATH and available to Clojure, but we still have to explicitly
import the bits of it we want:
(ns clojure-qt4-demo (:import (com.trolltech.qt.gui QApplication QPushButton QFont QFont$Weight) (com.trolltech.qt.core QCoreApplication)))
ns creates a new namespace, and provides convenience syntax to import Java classes at the same time. It can also import other Clojure files or Clojure libraries, among other things.
Note immediately how this is nicer than Java. In Java you would be writing something like
import com.trolltech.qt.gui.QApplication; import com.trolltech.qt.gui.QPushButton; import com.trolltech.qt.gui.QFont; ...
And so on, over and over. If you're lucky you have a bloated IDE to type those things for you. In Clojure, macros let us factor out the repetition; we name the big long path once, and pluck out the things we want. The other option in Java is to import
com.trolltech.qt.gui.*, which pollutes your namespace unnecessarily; Clojure lets us succinctly take just what we want.
One thing I couldn't find documentation for is the way to import a static inner subclass of another class. To do this you have to use syntax like
SomeClass$SomeSubClass. My first thought was
QFont.Weight, but those don't work.
If you're typing all of this at a REPL, you now have to do:
to switch to the namespace we just created. Next we set up some convenience functions:
(defn init  (QApplication/initialize (make-array String 0)))
In Qt4, the first thing you always do is initialize QApplication.
QApplication is essentially a singleton class, and it has to be initialized via
QApplication.initialize(), a static method call, before you do anything else. In Clojure this becomes
(QApplication/initialize), which says to call the function or static method called "initialize" in the namespace called "QApplication". Static methods in Java become functions in a class-namespace in Clojure.
This function expects an array of Strings, which in a normal app would be commandline parameters. In Clojure we can make a native Java Array of Strings via
make-array. I just pass in an empty one because I'm running this from a REPL. Note, a native Java Array of Strings is different from a Clojure persistent array,
[ ]. This is a bit nasty, but necessary for Java interop. Most of the time you will be able to get by with
[ ]. Next:
(defn exec  (QApplication/exec))
This function (again a static method call) is generally the LAST thing you do in a Qt application. It starts displaying widgets and fires up the event loop.
A gotcha here is that certain things must happen in a certain order. A Qt app looks something like this:
- Initialize and set up all of your widgets etc.
- Either the program exits, or go to step 1 to start over.
For example if you try to exec() before you initialize(), it does nothing at all. If you call initialize() more than once, it dies with a
RuntimeException. When I was playing around with this at the REPL, it was very common that I called initialize multiple times by mistake. It's often safe to swallow the exception though.
All of that bookkeeping really wants to be made into a macro. Here's a simple macro:
(defmacro qt4 [& rest] `(do (try (init) (catch RuntimeException e# (println e#))) ~@rest (exec)))
This kind of thing lets you avoid the insanity of Java, which forces you to re-type exception-handling code and other boilerplate code over and over and over. You can abstract that all away in Clojure.
Macros in Clojure are much like Common Lisp macros.
~@ is a splicing-unquote, like
,@ in Common Lisp. Another interesting thing here is
e#, which is a shorthand way to create a gensym. It creates a unique symbol; this prevents our macro from stepping on the toes of any other symbol called "e".
Using this macro we can finish our simple example:
(defn hello-world  (qt4 (let [app (QCoreApplication/instance) button (new QPushButton "Go Clojure Go")] (.. button clicked (connect app "quit()")) (doto button (.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value))) (.setWindowTitle "Go Clojure Go") (.resize 250 100) (.show)))))
EDIT: Updated doto's to reflect Clojure's latest syntax.
hello-world function is pretty straightforward. It makes a button, sets up an event handler that closes our app when the button is clicked, then sets its title and font, resizes it and shows it. Note how seamless this is. Aside from all the
.'s, you could barely tell this was Java. It's all tasty Lisp.
Clojure has a bunch of simple convenience macros that make writing Java less painful, two of which I show here. One of the biggest problems with Java is its extremely verbose and punctuation-heavy C-like syntax. Compare:
button.resize(250, 100); button.setFont(new QFont("Deja Vu Sans", 18, QFont.Weight.Bold.value())); button.setWindowTitle("Go Clojure Go"); button.show();
Look how much repetition there is in the code. button.blah, button.blah, button.blah. Repetition is bad. Clojure has the
doto macro which says "take this thing, and do a bunch of stuff to it" without having to retype it every time:
(doto button (.resize 250 100) (.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value))) (.setWindowTitle "Go Clojure Go") (.show))
EDIT: Updated doto's to reflect Clojure's latest syntax.
Another macro is
.. which says "take these things and put dots between everything", chaining method calls. So instead of
QFont.Weight.Bold.value(), you can say
(.. QFont$Weight Bold value). Handy.
To run this from a REPL, type
This app of course is a silly toy and just scratches the surface. But in my opinion, Clojure already beats all other Lisps by providing access libraries like this one. This is one of its killer features for me. The fact that you can seamlessly and easily use any Java library from Clojure is pretty amazing.
Why would you want to run Qt4 apps from Lisp anyways? Java (and thus Clojure) already have other GUI frameworks, like Swing and SWT and AWT, so why bother with this? Well, a few reasons...
- Because I can.
- Qt4 has a lot of impressive features nowadays. Run this Qt Jambi example to see some of those features.
- Qt's design may appeal more to you than Swing's, which can be clunky at times.
- Maybe you want to integrate well into KDE. I first started exploring GUI programming in Clojure when trying to make an icon sit in the system tray, and while Swing can do this, it's a bit painful to get it to look just right. It's trivial to do in Qt4.
- If I'm forced to write a Qt4 app, I'd much rather write it in a highly interactive and iterative way using Clojure than struggle with a write/compile/debug/recompile/wait-for-hours cycle. At the very least Clojure would be ideal for prototyping.
- Because I can!
Verdict: Clojure rules. But Java interop is just one reason it rules. Clojure has native Perl/Ruby-like reader support for hashes/arrays/sets/regexes/etc., support for easy and safe concurrency, cross-platform-ness galore, and a much more modern feel than Common Lisp. It's being worked on by some very smart people and the community is vibrant, enthusiastic, and welcoming. It has all the strengths of Java and Common Lisp, and very few of their weaknesses.