Reactive Swing and Observables Pt 3
tom, 2012-02-14

The last post introduced Swing, identified it as an event-driven GUI framework, and talked about the Observer design pattern. In my opinion, Swing could stand to learn a lot from event handling in F# and the .Net Reactive eXtensions framework. Here’s why:

Events are not first-class citizens in Swing, and we cannot treat them as data. Instead, we have to intercept event packets via scoped interfaces (listeners) that are attached to event-producing Swing components. This usually results in creating ad-hoc implementations of listener interfaces. We do not have the ability to subscribe to a single event, or to compose the observation of events into new, compound events. Everything has to be handled inside an encapsulated method of an implemented listener interface.

The typical workflow for attaching a listener to something that produces events is: Implement a Listener interface/s relative to the events of interest. Typical Java listener interfaces are broken up into groupings of handlers for the events they implicitly ask a component to subscribe to. The consistency amongst groups is that every method in the Listener interface has the same signature (SomeEvent -> Void). For example, the MouseListener interface implicitly defines 4 mouse events via its handlers:

mouseClicked :: MouseEvent -> void
mouseEntered :: MouseEvent -> void
mouseExited :: MouseEvent -> void
mousePressed :: MouseEvent -> void
mouseReleased :: MouseEvent -> void

To handle mouse events for a JPanel, you tell the JPanel to attach a mouselistener with appropriately overridden methods. The idiomatic Clojure way to do this is to evaluate the .attachMouseListener method of the swing component, and pass it a Proxy of a MouseListener:

(.addMouseListener mypanel
  (proxy [MouseListener] []
    (mouseClicked [e] (println e))))

The net effect is that performing non-trivial GUI programming in Java (and to a lesser extent Clojure) requires dealing with attaching listeners, and never getting to see the data. Proxy mitigates a lot of this, but there is, I think, a better way…a way that’s more functional….

.Net handles this by publishing specific events of interest, and allowing anything to subscribe to them. Prior to the .Net Reactive eXtensions, and (in my case) the treatment of events in F#, .Net folks had to use delegates to handle events. Delegates are just statically-typed wrappers around functions….really a hacked lambda function. In fact, the F# team realized this (along with the Rx guys), and decided to promote events (like functions) to first-class status. The end result was an elegant library for extending the basic sequence transforms (map, filter, reduce, scan, etc.) onto .Net events, to produce elegant and composable events. You get the ability to treat events as streams of observed values, driven by IO, which can be composed just like functions.

During my stemming, I realized that it’d be really useful to implement the .Net/F# view of first-class-events in Clojure. With a suitable infrastructure in place, I could then write some plumbing libraries to extract the events described in the existing Java Listener interfaces, turning them into streams of observable values amenable to sequence operations, rather than hiding them away behind Swing’s interfaces. Separating the data from operations that work on data felt like a more functional, and Clojurian approach.

Next: an implementation of observers in Clojure, generic events, event combinators, and using reflection and macros to “wrap the crap.”