Reverse Your Arrows

Matthias
11 min readSep 11, 2016

--

I can now safely say that I have been programming for the better part of my life. If you’re like me, you might still be astounded by the amount of things you learn day in and day out, but the moments of real insight become rarer over time. I mean those insights that change your perspective on things in some profound and lasting fashion. Often those insights mark the difference between naming things and knowing things.

For instance, you might be able to name a few design patterns. For some of them you might have internalized how and when to apply them. But how often do you step back and ask yourself whether they are governed by some underlying principle, a meta-pattern, so to speak? One pattern to rule them all? That could mean knowing design patterns and their relationships.

The benefit of developing an understanding of how patterns are connected is that it becomes easier to judge whether a new problem can be fitted into a familiar problem space, or whether you can amend an existing solution for a related problem and apply it to the new problem instead. I believe that one such governing principle that is key to solving problems in their respective problem space is found in understanding the problem spaces’s arrows, and manipulating their direction to your benefit.

That sounds awfully abstract, so let me start by exploring why this sounds difficult to comprehend at first. Bear with me though, I promise that soon you’ll start seeing arrows everywhere.

Enslaved by intuition

We, as people, are fundamentally forward-oriented in our thinking and actions. It is ingrained in us by how we perceive the world: time flows forward, effect follows cause — despite most physical processes being indifferent to it. It’s common sense: a shattered glass dropped to the floor has not yet been observed replacing itself to its original state. People are actors in this world, so we naturally assume that this is transitive: to solve a problem, we start with ourselves and things created and put in motion by us, which in turn create other things and put yet more things in motion. In other words, an act of creation or derivation usually consists of forward pointing arrows.

In this article I will try to explain why going from forward-pointing to backward-pointing arrows, while often difficult, is the key to understanding many patterns in programming and software architecture. This seems to be at odds with our intuition, which leads us to tackle complexity in the most straight forward way first. After all, there is a reason it’s called straightforward, and not straightbackward. Unfortunately, while straight forward often means simple, it doesn’t always mean good.

Reversing a familiar thought process often sounds absurd at first, but can suddenly lead to elegant solutions for difficult problems. When Ptolemy declared the Sun and stars to circle the Earth, he was victim to his own common sense. Today the idea of us being the center of the universe sounds ludicrous, but that’s purely thanks to someone with a strong sense of abstraction and rationality disconnecting themselves from intuition to consider a higher level view: that there are other points of reference than just Sun and Earth, and we’re not special after all. That someone was Copernicus and he rocked our world by reversing Ptolemy’s arrows.

Where Ptolemy assumed that everything originates from Earth and everything else derives from it (leaving some inconvenient questions unanswered in the process), Copernicus realized that the motions of the heavenly bodies made a lot more sense if the Sun was dominating Earth and the other planets instead. The accidental complexity of Ptolemy’s model (the infamous epicycles) suddenly disappeared. While it’s unlikely that we’ll arrive at similarly Earth shattering conclusions in our programming careers, we can still learn from this and apply it in the small.

In software development, you’ll find two kinds of arrows that are essentially universal: control flow and information flow. The examples of poorly written software rooted in forward-pointing control and information flow are countless: complex, unmaintainable, incomprehensible software where every cog in the system acts on other cogs instead of being acted on. Components taking control when instead they would better left being controlled, components talking when instead they should shut up and listen. The first arrow reversal I would like to explore is therefore Inversion of Control (IoC), also known as the Hollywood Principle — “Don’t call us, we’ll call you!”

Reversal in object hierarchies

One of the earliest recollections I have of reversing arrows to solve a programming problem in some structured way is the Template Method Pattern. In class based Object-Oriented Programming, subclasses often specialize their superclasses by overriding their behavior. In my programming rookie days, I’ve been guilty of writing despicable code like the following:

class A {
void performSteps() { // do A specific things }
}
class B extends A {
@Override void performSteps() {
doFirstThing();
super.performSteps();
doLastThing();
}
private void doFirstThing() { ... }
private void doLastThing() { ... }
}

What’s happening here is that B is “decorating” A’s behavior by first unhinging the inherited method by overriding it and then wrapping extra steps around it. While this might look harmless at first, it quickly leads to code that is difficult to understand or even misbehaves, since it’s subject to subtle call ordering issues. Worse, imagine if we make the super call conditional and A’s behavior can suddenly “disappear” based on changes in program state. The crux of the matter is that B is trying to take control over A. We can instead arrive at more solid, more readable, more maintainable code by applying the template method pattern:

abstract class A {
final void performSteps() {
onStart();
// do A specific things
...
onFinish();
}
protected abstract void onStart();
protected abstract void onFinish();
}
class B extends A {
@Override void onStart() { // do first thing }
@Override void onFinish() { // do last thing }
}

That is, we inverted the arrow by yielding control flow back to the superclass that defines the algorithm. The subclass instead becomes a passive actor — it’s not calling anymore, it’s being called. This is an example of control inversion.

Reversal of Dependencies

One of the most well known applications of IoC is the dependency inversion pattern, better known as dependency injection (DI). I was first introduced to the idea as part of a practical course during my Software Engineering curriculum at university ca. 2004 in form of the Spring IoC container (anecdotal fact: dependencies were then wired via XML files, believe it or not.) Let me stress, however, that it is a common misunderstanding to equate DI with a particular framework or library. In fact, no tooling other than your programming language of choice is required to practice DI. All you need is to do is understand the arrow of this problem space: you don’t create objects, they are provided to you.

Before the wide spread recognition of DI as a good practice, it was commonplace to “pull in” dependencies directly at the call site. In its mildest form this meant directly allocating a collaborating object, at its worst it meant reaching out to global state (that includes singleton objects, which are just global state in disguise.) In both cases we are coupling ourselves to the execution context in which we act. If our collaborator is an object that performs expensive side effects (such as network or disk I/O), we lose the ability to take the caller out of its “natural habitat” and into, say, a test or staging environment instead. Violating dependency inversion therefore erodes modularity and increases coupling across your entire system.

We can easily solve this problem by reversing the control arrow from defining dependencies to declaring them. When practiced rigorously, DI means that every single module in your system attempts to push the responsibility of provisioning collaborators to its owner, so long until there is no more owner, i.e. you’ve reached the root of your object graph. There are different ways of how this can be achieved, largely dependent on language idioms, but constructor injection, method injection, the cake pattern and function composition are some that come to mind.

The Law of Demeter

Staying in the object world a while longer, I cannot leave the Law of Demeter (LoD) unmentioned as an example of arrow reversal that has a profound effect on the readability and maintainability of code modules. The LoD is sometimes referred to as the “Tell, don’t ask” principle of object-oriented programming. In a nutshell, it attempts to minimize the distance information has to travel over a series of method calls. Consider the following code snippet:

obj.a().b().c()

Here, obj wants to invoke the behavior of a method c, by traversing the graph of collaborators obj’ (defining a), obj’’ (defining b) and obj’’’ (defining c). This is sometimes called “train wreck” code due to the shape of the invocation chain.

The problem here is that obj accidentally assumes knowledge of the entire span of methods exposed by its direct and indirect collaborators. In other words, the influence of obj increases rapidly with the distance of the behavior it summons. LoD therefore demands that behavior and data are to be co-located with the object that requires it, either by defining it on obj to begin with or by keeping it in a collaborator that is in direct reach.

Obeying the LoD is a form of arrow reversal. Instead of moving forward along a dependency graph, we reverse the arrow and move the required functionality closer to the place where it is required.

Event driven programming

So far we’ve been looking at control arrows. Let’s turn our attention to information arrows, specifically event driven programming (EDP). In its simplest form, EDP can be practiced by applying the observer pattern, a prime example of the “Hollywood Principle”. Without events, any code module must ask for the data it requires, potentially blocking on a function call until that data becomes available. This model quickly falls apart when blocking is simply not an option, such as in user interface programming, where we must not stall the rendering thread.

We can solve this problem by pushing data as events instead of pulling it from the source. This is sometimes called the Publish/Subscribe model, or pub/sub for short. The arrow kind here is therefore one of data flow direction. Pub/sub completely decouples us from making decisions about when an event source is ready to send more data. This is of paramount importance in asynchronous or distributed systems, where this is difficult to control or predict.

There is another interesting relationship here that I wouldn’t want to leave unmentioned: the relationship between events and application state. With pub/sub, an event is typically sent as a result of mutating application state to inform observers of the change. You can turn this relation on its head by reversing the arrow and derive application state from events instead. This can be achieved by modeling every state transition in your system as a persisted additive event, thus obviating mutable storage of state. In case of state loss or when retrying failed events, this log of events e’, e’’, … can be replayed onto your application to partially or fully reconstruct a particular state sk:

e'  => s'
e'' => s''
...
ek => sk // replaying [e'...ek] yields sk
ek' => sk'
...

This practice is called event sourcing and it’s one of the most ingenious arrow reversals I’ve seen. It leads to highly predictable, reproducible, and fault-tolerant systems and can be combined effectively with other architectural patterns such as Command Query Responsibility Segregation (CQRS) to obtain systems with high cohesion and low coupling.

Reactive programming

If one takes the idea of EDP to its ultimate conclusion, one arrives at the concept of reactive programming, data flow programming, or stream programming. In reactive systems the result of expressions is evaluated lazily so that state changes are never polled for, only pushed. This is in stark contrast to traditional imperative programming styles advertised by languages such as those of the C family, where you always have to ask for the value of something. RP is best explained with a very simple example:

x = 1
y = x + 1
x = 2
return y

What would you say is the value of y by the time we return? If you answered 2, then unfortunately your intuition has been successfully undermined by years of programming in imperative languages, because every mathematician would tell you the answer is 3. The trap into which you fell is that imperative languages make you think in instructions, not in relationships, because instructions are really all a computer understands. But when we think in relationships, then what this code really says is that y is completely dependent on the value of x, so when we look at y, it needs to either lazily evaluate x or have been updated accordingly by the time we changed x. That’s why in any reactive programming environment, 3 is returned.

This constitutes a reversal of arrows, since we completely changed the direction in which things evaluate. In imperative programming we only see change when we ask for it. In reactive programming we never ask: we declare functional relationships and let state changes trickle through the system, updating all dependent bindings in the process.

In the ReactiveX family of libraries, this pattern is implemented in the Observable type, which is the conceptual dual to iterators. When I first learned about reactive programming in December 2012, I thought the idea so radical and appealing, our team and I immediately started to migrate the SoundCloud Android app to an Rx driven architecture. We haven’t looked back.

Typeful programming

I would like to conclude this post by shifting perspective just a bit. We’ve been talking about design and architectural patterns a lot, and how arrow reversal can help us solve problems in surprising and powerful ways. However, we were just enumerating examples and naming things is not knowing things.

That said, I think the best way to get to familiarize yourself with “arrow based thinking” is to practice typeful programming. Type systems are deeply connected to category theory, the field of mathematics which attempts to discover and formalize relationships between structures in mathematics itself. Or simply put, it studies arrows and how they connect objects in some interesting way. This is extremely useful when reasoning about relationships between types, which is what compilers spend much of their time on.

In the recent years, more and more higher-level languages have started to make these “objects” and “arrows” available not just to the compiler, but to the programmer. If this sounds too abstract and you’re wondering why this is useful, consider this function in Scala:

def parseInt(s: String): Option[Int] = { ... }

Here we have a function that takes a String and knows how to convert it to an Int; however, the string might not actually be a number so this operation can fail. This is expressed in the return type Option, which is either Some(x) or None. The task is to produce from a list of strings a new value that is

a) Some(list) of all the numbers, if all strings are numbers, or
b) None, if at least one string is not a number.

This is not a contrived example; in fact, I had to write code like that just the other day in order to validate a list of HTTP request parameters. If you’re already thinking of how to traverse lists in loops and what not, let me stop you right there: category theory already solved it for you. Here’s how it’s done using typelevel’s cats library (I borrowed the example from the docs):

List("1", "2", "3").traverse(parseInt)
res0: Option[List[Int]] = Some(List(1, 2, 3))
List("1", "two", "3").traverse(parseInt)
res1: Option[List[Int]] = None

So what is this magical function traverse? You might think it has been written specifically for turning lists of strings to options of lists. Nothing of the kind. In fact, we can use it with any applicative functor; because that’s what List and Option are, and there are well known arrows that connect these structures. This is an example of how studying the relationships between things can ultimately safe you time.

Conclusion

I hope I could demonstrate how understanding arrows between things can be extremely useful and exploited to your benefit. While it’s not our default modus operandi to think backwards, it can be a powerful problem solving tool. There is a lot more to be said about the topic, since arrows are important not just in programming. Ever put yourself in someone else’s shoes? Arrow! But I’ll leave that for someone else to explore.

So next time you’re faced with a difficult problem: reverse your arrows.

--

--