Wednesday, August 12, 2009

Composition over Inheritance: The functional approach to GUI Programming

Ever program with a GUI toolkit? Far too many classes and inheritance for some of the small tasks, right? This is especially true of event handlers meaning this isn't just GUIs though, it's any type of FSM (Finite State Machine) setup. The problem is that the event callback has to do some randomly scoped work that the actual FSM code can't be designed to handle easily.

So there are two approaches taken:

  • Event handlers are a function that takes a pointer and a userdata struct (this approach is often taken in c/c++).
  • Event handlers are an object which implements an interface (an adapter/wrapper class).

The second approach is the one Java often takes. It's painful and you will usually get buried under many trivial classes (inner-anonymous or implemented in their own separate file) and syntax. This is a large barrier and makes a simple problem more complicated. I prefer a solution closer to the first, but the way it is typically implemented is still wrong! There is almost just as much syntax wasted what with creating the function somewhere else in the code and declaring, packing, and unpacking each of these userdata structs. It's just ugly. It doesn't need to be so bad though.

Essentially, one needs to gain an insight of what information the callback actually needs access to. These would be the fields in your userdata struct or what the adaptor classes access from the instantiating class. This tends to be the entire class (if applicable), one or two parameters, or nothing at all. An FSM model really just wants to do the following:
If condition x, perform callback y
That's pretty much the conceptual for every given state/node for all pairs (x,y) of condition/callbacks each respective node contains. Simple right? Well, we just need to get back to our conceptual roots. Enter partials or currying from the functional languages to make that possible.
You might have heard of lambdas or anonymous functions before. Often one must use these to get away with partial function application if not in a purely function language. Languages like Python, Ruby (blocks too), lisp, haskell, and even Matlab all have these in one form or another. Some like, python, can make it a little more tricky to use partials than it should be but a fairly natural way exists. Now Python (and thus also Ruby, to some degree) are going to be the focus of the rest of this entry because these languages have bindings to every GUI toolkit known to man. They are also dubbed RAD (Rapid Application Development) or prototyping languages because how fast one might implement a fairly complex program.

Ruby implements this a little more correctly so not much needs to be said there other than an example like this:
def multiply(x, y); x * y; end
multiply_by_z = lambda { |x| multiply(z,x) }
In python, we can get away with this like the following:
from functools import partial
def multiply(x,y): return x*y
Now here's how to code less when hooking things up in FSMs and thus GUIs. Remember how we really were going through a roundabout way of just saving/packing parameters so that we could use them later in a callback for an event? Well, now you just make your function up or perform your adaptive-behavior in a simple lambda (if applicable) or nested function (this part isn't that new except in languages such as Ruby/Python you have nested functions which allow you to put the callback right where it makes sense and possibly not making you name mundane things which are obvious like the multiply above). Ruby automatically saves the arguments you've specified in the function call already and in python you wrap them with a partial object. So now when you hand the callback to your FSM model addEventOnCondition(callback()) or addEventOn(condition(), callback()) methods you will notice that on the FSM side, you only need to take void/no argument functions! The callbacks already have had their arguments set, they just haven't been called yet. This simplifies the FSM code as well as gets rid of all those userdata struct classes. There is also no need for any adapter classes. The FSM classes implement just what they conceptually need. Functions/Event callbacks are handed just what they need before their actual application, right where you have the most natural access to them. The syntax barrier is low. The code is short and as close as currently possible to the ideal. This all translates into less effort spent and less bugs to deal with.

Using partials/currying makes sense. I encourage every programmer to try and use these constructs, it's staggering how much they can simplify your designs. Bonus points if you start using map/each instead of writing for-loops all the time.

No comments:

Post a Comment