Designing an API
Throughout these examples, we have tried to highlight the importance of iterating on the code that was written, and thinking about how you want to interact with your codebase.
This section expands on these ideas, and goes over a couple of examples where designing the interface comes first.
Introduction
If you are familiar with the mean() function, it takes an array of numbers of any size. So both function calls below are perfectly valid:
a = [1, 2, 3];
b = [7, 8, 9, 10];
mean(a);
mean(b);
Imagine if, instead, you had to know the exact number of elements in the array and call a different mean function for that number of elements. Sounds bad, right? Let me show you just how bad.
a = [1, 2, 3];
b = [7, 8, 9, 10];
mean_3_num(a(1), a(2), a(3))
mean_4_num(b(1), b(2), b(3), b(4))
Guiding principles
I think the ideal way to interact with code is that
-
It should read like a set of instructions;
-
It should be opinionated, so that I don't need to configure everything;
-
The editor's autocomplete should help me find what I'm looking for.
-
At a glance, the user must be able to understand and modify what they are doing without having to know any details about implementation.
An example
Consider this example from our codebase:
experiment
.create_ap_envelope() ...
.exclude_specimen("3") ...
.average() ...
.split_flexion_extension() ...
.plot();
It emulates the exact way we discuss data processing, providing flexibility in both the order in which functions are called and in the arguments. For example, if we don't want to produce all different types of plots, we can limit to any number of them:
plot("anterior");
plot(["anterior", "medial"]);
How was this achieved?
Not all of these functions are methods for the same class.
We intentionally produce objects of different classes in order to have a different set of functions being suggested by Matlab's autocomplete.

In fact, because exclude_specimen(), average() and split_flexion_extension() all return instances of Envelope, we can call them in any order.
The signature of all these methods is pretty similar:
classdef Experiment
methods
function envelope = create_ap_envelope(self)
envelope = Envelope(...)
end
end
end
classdef Envelope
methods
function self = exclude_specimen(self, specimens)
end
function averaged_envelope = average(self)
averaged_envelope = EnvelopeAverage(...)
end
function plots = plot(self, directions)
plots = Plot(...)
end
end
end
These are based on the idea of a state machine, in such a way that each action (function call) modifies the type of object you're dealing with.
The underlying mathematics is precisely the same, but there is a programmed, concrete difference between a list of numbers that represents an Experiment or an Envelope or an EnvelopeAverage.