This is a read-only archive!

Clojure Qt4 system tray mail checker

I uploaded some Clojure code to github for anyone brave foolish enough to try it out. It's an app that sits in your system tray, checks one or more IMAP or POP3 mail accounts, displays an icon with a number overlayed indicating how many messages you have, and displays a right-click menu with the message senders and subjects. It's not as pretty as it could be and I abuse macros a bit, but it works. The icon is transparent too, which is the main reason I wanted to use Qt4. (Other than a general dislike of Swing.)

mailtray

My next task is getting this to run in a background thread rather than blocking the main thread, so you can more easily fiddle with its guts from the REPL while it's running. I'm 50% of the way there but still need to figure some things out.

Here are some good and bad things I experienced with Clojure while playing with this.

The good

  • Java integration really is seamless. Aside from the dot's and new's, you don't even notice you aren't writing pure Lisp. Qt is also easy to use from Clojure. You can even do SLOTs and SIGNALs pretty easily using Clojure proxy objects. (I didn't need them for this app, but playing around I got them to work eventually. Check this site out for some examples, which I'm not sure work in the latest Clojure SVN but are still good examples.)

  • As you'd expect from Clojure, the concurrency part of the app was a no-brainer. There are a bunch of threads hammering on a single global hash list of email messages, and they're all forcing updates of the GUI, but throw a bunch of dosync's around certain bits of code and it all works without worry. (Qt's invokeLater is also necessary if you're updating a Qt GUI from more than one thread at a time.)

    My first version of this app had even more threads, with one thread updating the GUI every few seconds, and another master thread managing all the mail threads. It also worked fine. It was overkill so I scaled it down, but it's really nice how you can coordinate a bunch of threads and don't have to worry about deadlocks and race conditions and whether your data structures are thread-safe.

  • I've written very small Qt4 apps in C++, Ruby and Python before but it was never fun. In Clojure you get a lot more instant gratification. You can compile a single function or a single file by banging on some Emacs shortcuts, no need to run any kind of external make program, no need to restart your app from scratch from the commandline every time you make a tiny update. You can spawn threads, then play at the REPL while they're running, to tweak their behavior. You can have threads calling some function, and recompile the function so the thread calls the new version next time, without killing or restarting the thread. Playing with any Lisp REPL is interactive and fun.

  • One problem that's always being re-solved is how to acquire a resource and make sure it's un-acquired when you're done with it. Some languages use try/finally blocks. In Clojure you can do that too, but go one step further, and use macros to write the try-finally blocks for you. So you say (with-resource [some-resource] (do-stuff)), and stuff will be done, and you can be sure the resource will be disposed of afterwards no matter what happens.

    You can do the same kind of thing in any language with lambdas and some kind of except-handling of course. That's how Ruby does it. Or any language with construction/destruction hooks. But macros are elegant in their own way. The code is more natural-looking and less verbose, and it's all set up at compile time rather than runtime.

  • Not sure if it still does, but a week or so ago, I threw this onto my Windows box without ever having tested it, ran it, and it worked! It wasn't pretty, because Windows' tiny little taskbar is too small to be able to read the number or see the icon. But it's still very nice. A multi-threaded GUI app, albeit a simple one, that worked cross-platform without even the slightest bit of effort.

  • Functional languages are fun. Clojure isn't 100% pure like Haskell, and that's a nice thing when it comes time to write an app like this which has lots of state. Mutable references to immutable objects suits me just fine. Clojure is still functional enough to keep things sane and clean.

The bad

  • Clojure is still evolving very fast. Clojure evolved enough while I was writing this program that I had to change a bunch of things that suddenly stopped working. There's a good chance this code will not run in a month or two without changes.

  • Java, Java, Java. I'm using Javamail to connect to a mail server, and that library is a pain.

    First you get a properties object, and send it to the session object, from which you get a store object, which will let you get a folder object, which you can open (passing in some flag objects), and after creating a fetch profile object, you can then actually fetch some of the message objects. Whose parts are objects whose contents you have to fish out.

    It's not pretty. Clojure makes it easier to write, and macros mean you only have to write it once. But you still have to wade through this mess of objects and immense API of interfaces and inner classes and total bullcrap. What would be 3 or 4 lines of Ruby is instead 5 or 6 screens-full of Java, to do something that shouldn't really be that complicated.

  • Clojure modules/packages/"libs" (whatever you want to call them) are still confusing to me. To include a library of code from one file into another, your choices are: load, load-file, use, refer, require, import, and ns which wraps a bunch of those. They all have slightly different syntax. Some take strings, some take symbols. Some take extra flags, some don't. Some look in CLASSPATH, some look for files, some figure out the file from a symbol name. Some require a certain directory structure, some don't. It's highly confusing and you can't tell just by looking which does which.

    Now that AOT compilation is here, it can be even more confusing, because you can compile files into .class files, into a separate directory, and then load that code, but only if you have all the directories set up properly beforehand.

    The documentation on this (and on many other things) is present and fairly complete, but bare-bones and sometimes difficult to understand unless you're very familiar with Clojure already. If I didn't follow the Clojure mailing list closely, I'd be lost.

  • It's probably the fault of Qt Jambi, or maybe just the JVM, but running this Qt app is very memory-intensive. 60-70MB to run an app that consists of nothing but a system tray icon and some worker threads? That hurts. Admittedly I didn't do much to pare this down, but it seems like the JVM will happily expand gas-like to fill its container of available RAM unless you put a tight leash on it. Once started, the JVM / Clojure runs fast (and it had better, all things considered).

  • Laziness still bites me sometimes. I was making some lists of mail folders and trying to fetch messages from them. Because of laziness, the fetching of the messages was delayed, until AFTER the folder was closed, which bombed. Took me a while to figure that one out.

    You have to sometimes explicitly force Clojure to evaluate things if you want to know when they happen; otherwise they're delayed until some point in the future when you ask for their value. This is a pitfall of laziness which maybe I'll get used to eventually.

  • Clojure code is dense (like all Lisp code). Maybe too dense?

(dosync
  (ref-set *mailspecs*
           (into #{}
                 (map #(agent (reduce conj (struct mailspec) %)) specs))))

A LOT is going on right there. It may just be poor style on my part. But Clojure has a lot of powerful seq-manipulating operations, iteration functions and reduction functions, and it's easy to write an 8-level deep tangled web of closures and function calls. You have to keep a lot of things in short-term memory to figure these things out mentally. They also don't read sequentially left-to-right. Usually you have to read them inside-out or right-to-left or some combination.

Conclusion

All in all it's been fun. The language is growing and changing fast, but it's already more than usable. Functional programming is still odd to my poor mind, but that's as much the fault of my poor mind as anything else.

December 12, 2008 @ 1:14 PM PST
Cateogory: Programming
Tags: Qt4, Clojure

4 Comments

Jamie
Quoth Jamie on January 30, 2009 @ 4:04 PM PST

I was playing with jambi and clojure during the summer and managed to get signals/slots working from clojure. Its a bit fiddly because jambi expects every signal to be a java field in a named class and every slot to be a java method in a named class. The code is probably bitrotted by now but its here

http://sites.google.com/site/musingsbeneath/Home/inkling.tar.gz?attredirects=0

if you want it. Most of the action is going in in slot/ and com/

Java variety is not reuse « Theory matters
Quoth Java variety is not reuse « Theory matters on February 07, 2009 @ 5:22 PM PST

[...] my big “Java hate and love”. But this morning I was reading around about Clojure when I found this on briancaper.net referring to JavaMail API, that well explain the worst Java pogrammer [...]

angel
Quoth angel on July 28, 2011 @ 3:03 AM PDT

60 or 70mb??..that is really a lot of memory for a basic app...do you know other good alternative for gui in java...swing suck..my options were or qt or swt..qt look more clear and simple...but if it is memory eater I think swt would be a better option...thanks!!

Brian
Quoth Brian on July 28, 2011 @ 3:14 AM PDT

Seems like Java GUI apps always suck up a ton of memory regardless of what framework you use, unfortunately.