The flexibility and usefulness of an API relate directly to its orthogonality. But what does “orthogonal” mean? It’s a term that’s tossed around a lot in programming, with varying degrees of clarity.

### Angles of independence

The dictionaries I checked at least agree on a few common definitions: lines that meet at a right angle, statistical independence, and a few list “sharply divergent features”. These concepts are closely related.

If we consider a basic euclidean space, a graph, we have an X axis and a Y axis. These two axes meet at right angles (one of the definitions). Any combination of X and Y values can be shown on the graph, neither limits the other (part of that independence definition).

Let’s consider a practical example. Your phone has a volume control, a brightness control, and a certain amount of free space left. Obviously the value of one doesn’t depend on the value of the other. You can change your volume without changing the brightness. You can install a new app, which takes us space, without having the volume turned down. These are all “sharply divergent features” of the phone.

If we did a user survey on these features we might wish to plot the results on a graph. Each value would get it’s own axis, we’d need three of them: X, Y and Z. This is how this logical definition of orthogonality ties back to the mathematical one. We’re literally plotting our three features, volume, brightness, and space, on perpendicular (right angle) axes.

In statistics, these values would be orthogonal if they were truly independent: the value of one didn’t help predict the value of another one. I can’t say for sure if that’s the case here. We might find out that people with bright screens like to have their phone louder for example. Nonetheless, the concept of these “different” aspect is paralleled in the statistics meaning.

### But programming?

That’s nice and all, but what about programming? What does it mean to have an orthogonal language, or an orthogonal API?

Let’s contrast two relatively common examples:

1 2 3 4 5 |
var user = LoadUserFromYAMLFile( filename ) var stream = OpenFile( filename ) var dataTree = ParseYAML( stream ) var user = ExtractUser( dataTree ) |

The first function, `LoadUserFromYAMLFile`

is specific and offers little flexibility. It creates a fixed relationship between the user, the YAML format, and the filesystem. The second form distinguishes each aspect. `OpenFile`

creates a generic stream. `ParseYAML`

can work on any type of stream, not just a file. `ExtractUser`

only cares about having a tree of data, it’s totally irrelevant that it was YAML, or that it came from a file.

The second set of functions are more orthogonal than the first. We’ve built a program using several lower level building blocks. It’s easy to see how we can substitute functions like `OpenURL`

, `ParseXML`

or `ExtractDinosaur`

in this code.

We can also consider this on a graph. Let the X axis be an enumeration of all the ways we can create a stream object. Let the Y axis be an enumeration of all the ways to parse data. In an orthogonal API every combination of these features is supported: every value of X works with every value of Y.

In a non-orthogonal API you’ll find that only some values of X work with certain values of Y. Perhaps some parse functions don’t support all stream types. It could also be an indication that the abstract concepts are entirely absent: if you don’t have a stream you can’t even enumerate the possibilities to create a stream. This is how `LoadUserFromYAMLFile`

fails to be orthogonal: it would need an axis of its own, it’s hard to enumerate other options, and it’s not clear to what other features it’d be orthogonal.

This is not a judgement about functions like

`LoadUserFromYAMLFile`

, I’m just using it to contrast and explain. In many domains, especially as we reach high-level apps, we find the need for orthogonality diminishes. But we would still hope that this convenience function is built using lower-level orthogonal features.

### Interfaces and parametrics

Interfaces and parametric types are often found in orthogonal APIs. Again, by contrast:

1 2 3 |
defn sum_integers = ( values : array｢integer｣ ) -> ( : integer ) { ... } defn sum = ( values : enumerator｢numeric｣ ) -> ( : ) { ... } |

The `sum_integers`

function works only with an array of integers. Of all the possible list types, and of all the possible data types, only a single combination is supported. The second function `sum`

is more generic. It uses an `enumerator`

interface to accept an enumerable data set: a list, a native array, a queue, and even hand-rolled types. It is highly orthogonal with data collections.

`sum`

is however limited to using `numeric`

types. By definition a `sum`

can’t exist on anything else. So while it is highly orthogonal, it isn’t completely orthogonal to the type axis: not all types in the language can be used. To get it completely orthogonal we’d have to go one step further.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
defn fold = ｢T｣( values : enumerable｢T｣, combine : ( : T, : T) -> ( : T ) ) -> ( : T ) { ... } defn sum = ( values : enumerator｢numeric｣ ) -> ( : ) { return fold( values, (x,y) -> { return x + y }) } defn concat = ( values : enumerator｢enumerable｣ ) -> ( : ) { return fold( values, (x,y) -> { return x ++ y }) } defn join_string = ( values : enumerator｢string｣, joint : string ) -> ( : ) { return fold( values, (x,y) -> { return x ++ joint ++ y }) } |

The `fold`

function works with any type provided it has a combining operator: something that takes two inputs and returns a combined result. We can use this highly orthogonal function to build our specialized `sum`

, `concat`

and `join_string`

functions, which help convey meaning in our higher level code. It’d get hard to understand if we only used the `fold`

function everywhere.

### Side-effects

Some references on orthogonality include side-effect free as a requirement for orthogonal functions. I think this is actually wrong as side-effects are a distinct concept. This could definitely emerge in an orthogonal design, but it’s certainly not an inherent trait. Nor do I find most programmers consider this as part of what orthogonal means.

Whether the code you’re writing needs to be orthogonal or not depends on who’s using it. General purpose libraries need to have a certain degree of orthogonality to be useful. High-level business apps tend not to need it as they are serving specific use-cases. Clearly my programming language Leaf requires a very high degree of orthogonality, as do most languages. I can get into this another time though.

Categories: Philosophy, Programming