…or how I learned to stop worrying and love synchronized maps and cross-sections
Functional reactive programming for UI development is becoming more well known in “mainstream” programming languages. Demonstrations like Flapjax were some of the first I saw of it. (Incidentally from an old Rice classmate — now a professor at Brown.) Eric Meijer’s tireless evangelism of the "Reactive Extensions" resulted in one of the great libraries from Microsoft and use in a number of places, including Netflix.
But I don’t think you should be interested in it because it’s cool. (Though it is.) Or that it makes asynchronous programming easer. (Though it does.) One of the primary benefits for UI programming is that a reactive stance makes decoupling complex UI more straightforward, and therefore enables plugin-based user interfaces possible. Not better, but possible.
A common case in geology visualization is the cross-section, whereby you draw a line on a map, then simultaneously show a slice through the earth vertically down from that line. As you might see on, say, the sides of the Grand Canyon. (The line on a map in this case is the Colorado River.)
In real use, it ends up looking like this, where a map (say a Google map control) has a movable line on it, often labeled with end points like A-A', or several pre-set lines (like A through K here.)
Each line is associated with a nice computer generated cross section, shown in a different control. Clicking or moving a line in the map results in the relevant cross-section being drawn. Here’s A-A'.
Two viewers — map and cross-section — each with lots of specialized behaviors. By all rights, each ought to be built as a standalone control in our UI toolkit. They’re each complicated enough, with noticeably different rendering behavior even for identical domain objects. A well, for instance, could be shown on a map as a point symbol, while on a cross-section you’d see the borehole itself as a line. (Cross-sections are interesting, too… how close does something have to be to the line in order to see it? You usually want a fudge of a few meters, or even hundred meters.)
The standard imperative way to coordinate the two controls would be to have logic in the map control that watched the line get clicked on, then tell the cross-section to repaint itself. But how many cross-sections? What if the user has two perpendicular sections up? Why does the map know about cross-sections anyway? It’s only in this to draw a line! Oh, and in 6 months someone is going to want to draw a seismic panel along the same line, and that’s a different plugin with different concerns. Do you want to re-ship the map control to learn how to update seismic panels too?
This is a recipe for trouble. The map has to know about everything that could come after it, and tell them to redraw themselves. Which means it has to know about them, which means they have to register themselves with it, or be hard-coded into a web page, or something messy. Coupling everywhere. Easy at first, but impossible to untangle later.
Instead you really want to boil down their interaction to the smallest possible concept, and have an independent controller (perhaps even a specialized workflow) coordinate them all. Here’s where the reactive bit comes in handy. What is a cross-section window doing? It’s drawing a cross section through an earth model based on the location of a line. Really, just a line! It’s just a fancy subscriber to IObservable<Tuple<Polyline, EarthModel>>. The seismic panel? IObservable<Tuple<Polyline, Seismic3dDataset>>. The map? It does a lot, of course, but germane to this discussion it’s publishing the IObservable<Polyline> in the first place! A new Polyline is issued every time the user moves the line, or clicks on a different one.
In a simplified sense, the controller just has to say the following to kick it all off:
(where map.ActivePolyline is IObservable<Polyline> and
crossSectionControl.Line is IObserver<Polyline>… or more likely both are Subject<Polyline>)
Yes, you need a controller to wire it up, and the controller knows about these visualizations (or at least all visualizations that can render Polyline-inspired things), but now the windows don’t need to know about each other, and you never have to think about what situations require re-rendering. Earth model changes? Cross-section updates. Line moves? Cross-section and seismic panel update. They do the work themselves, because their rendering is defined as a reactive function of their inputs, not because someone came and told them to do it.
There is a coupling between these controls in the way we want them to work. But instead of them being coupled to each other in implementation, we posit a new controller that manages just the polyline interaction. When the user moves a line in the map, it publishes updates to its ActiveLine Subject, i.e. with ActiveLine.OnNext(someLine). The controller has arranged to subscribe both the Cross Section and the Seismic Panel to that Subject. Moving the line instantly triggers a redraw of the two other controls, yet those controls don’t have to know about the map, and the map doesn’t have to worry about it happening. The mechanics of the Cross-Section controller are themselves quite simple, mostly involving subscription setup and figuring out who should play the game with us.
Programmers can work on the four components here essentially independently. And if the interfaces are designed right, in another 6 months we can even build a Wheeler Diagram control… which draws yet another cross-section-like thing… and nothing else will need to change!