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.”