Clojure is touted as a dynamic language with Lisp dialect for the JVM. Clojure has tight integration with Java, and claims to simplify multi-threaded programming. One way it does this is by offering support for Software Transactional Memory, or STM. STM is an abstraction over the more typical thread library lower level constructs such as locks, mutexes, and monitors. It is often argued that these constructs make it inherently difficult to read, write, test, and verify non-trivial multi-threaded programs with. The Haskell community seems to be embracing STM lately, and while I am a bit skeptical that STM is the best solution for the locking problem, it does alleviate the programmer from having to write correct locking code and instead worry about what objects or state should be locked in the first place. Clojure offers dosync, ref, set, and alter as STM constructons. Let’s see how Clojure/Java interop fairs with a simple grayscale image converter using the Java Swing library. The next Clojure post will go into more details about the STM and the other Clojure approaches to concurrency.
This program uses a global binding m_image to the image to apply the grayscale function to:
(def m_image (ref nil))
The only way to change the value of the image is within a transaction, which you must specify via the dosync macro. The expressions of the dosync macro should not generate any side effects, because transactions that fail will be retried until the transaction succeeds:
(dosync (ref-set m_image (grayscaleImage (deref m_image))))
The deref macro will make sure that any reads to m_image are consistent, returning either the current in-transaction value, or the most recently committed value. Here is the code that draw the image, using deref to get a consistent value of image:
(. g (drawImage (deref m_image) 0 0 nil)))
Proxies are syntactically easier to look at once you get used to them, and certainly no worse that anonymous inner classes typically seen in Java Swing code. Here a FileFilter implementation is provided that overrides the accept method:
(def imageFileFilter (proxy [FileFilter] []
(accept [f] ;; implementation here
One thing this program does not address is threading. Typically, when performing any operation that may take a large amount of computation time, such as BGR to grayscale conversion, a new thread would be created, and any updates to Swing components would happen with a SwingUtilities.invokeLater call, to put the updates on a queue, and the AWT Event Dispatch Thread will process that queue in a single thread. Here we apply the grayscale operation in the same thread that the button event was fired on. A good discussion of proper thread handling with Swing and Clojure can be found here on the Clojure Google Group.
The entire source file is available here.