Last week, I was working on my book and showing how different functions would be implemented. But I kept having to explain that this wasn't an implementation. Instead, we were writing a function in the specification, although we wrote the spec in the same language as our production code. It was awkward. After several tries, I decided to distinguish between two synonyms.
In general, defining and implementing a function mean the same thing. Both words refer to writing the code for a function. But I've decided to make defining a function a special case of implementing it. Implementing means writing the code for a function. But defining means writing the code for a function only in terms of other domain functions without any implementation details.
For instance, here is a definition of pizzaPrice()
, which calculates the price of a given pizza:
function pizzaPrice(pizza) { //=> number
return sizePrice(pizzaSize(pizza)) +
toppingsPrice(pizzaToppings(pizza));
}
It's a definition because it only uses domain operations such as +
, sizePrice()
, and pizzaToppings()
.
However, here is an implementation of pizzaSize()
that does make use of implementation details:
function pizzaSize() { // => Size
return pizza.size;
}
Namely, the function assumes that `pizza` is an object and that the size of the pizza is under the `.size` property.
This distinction gives several benefits:
I avoid awkwardly explaining why implementing a function is not an implementation.
Suppose we can define a function purely in terms of other functions (defined or otherwise). That function can exist even before we finalize any implementation details, even as we iterate on our specifications.
The ones that require implementation details are the functions that characterize our requirements.
One way that writing helps clarify our thoughts is through a process similar to this. We begin writing, and some things we want to say are awkward. We search for existing words to make them comfortable but can't find them. So, we make the distinction ourselves, sometimes using existing words. I did this a lot in Grokking Simplicity. I take great care in word usage when writing on technical topics for long works like books.
An important reason to work on specifications separate from implementation is to be able to reason about your system's behavior before committing to implementation details. Coding those implementation details will hinder our iterations, and we'll learn more slowly. However, defined functions don't depend on implementation details. We can write those down early with no downside.
The surprising benefit of this distinction is that we know the functions we cannot define—those that make no sense without implementation details. Knowing what those are gives us the requirements for the operations our implementation must support efficiently.
For example, if I knew that I needed to implement these functions on ToppingCollection
:
function addTopping(toppingCollection,
topping) //=> ToppingCollection
function removeTopping(toppingCollection,
topping) //=> ToppingCollection
function howMany(toppingCollection,
topping) //=> number
And if I knew some basic properties of them, like that addTopping()
was not idempotent, these functions would help me choose a data structure to implement ToppingCollection
with.
Sometimes, we make awkward statements when explaining ideas. The awkwardness tells us that we need a more apt vocabulary. When we finally make the necessary distinctions, it can unlock new avenues for concepts and techniques. It's one of the reasons I love writing—especially long-form works like books. The only drawback is that I now think differently about a topic than most people, so I must work harder to communicate with them. But that's a small price to pay for the gift of better understanding.