From Generalized to Specialized

A function is worth naming even if you only call it once, why a specialized function should point at its nearest relative, and why parameter order matters.

September 13, 20263 min read23 / 27

Say you have one general-purpose function for hitting your API:

JavaScript
function apiRequest(endpoint, payload, callback) { // ... } apiRequest('/orders', { id: 42 }, renderOrder);

Nothing wrong with that call. It gets the job done. It also asks the reader to parse three unrelated pieces of information just to understand what's happening.

A Name Is Worth It Even If You Call It Once

JavaScript
function getOrder(payload, callback) { return apiRequest('/orders', payload, callback); } getOrder({ id: 42 }, renderOrder);

getOrder fixes the endpoint and asks for less. Compare the two calls: the second one names its own purpose, the first one makes you infer it from a URL string.

The common instinct is to only extract a function once you're calling it several times, some version of "don't repeat yourself." That's not the only reason to name something. Even a function called exactly once can earn its place, because a good name replaces "figure out what this line does" with "read what this line says."

Push it further. If part of your app only ever cares about the currently signed-in user's own order:

JavaScript
function getCurrentOrder(callback) { return getOrder({ id: currentOrderId }, callback); } getCurrentOrder(renderOrder);

Three calls, three levels of specificity, identical underlying behavior. Nothing about what actually happens changed. What changed is how much the reader has to work out for themselves at the call site.

apiRequest('/orders', { id: 42 }, renderOrder) general — 3 things to parse
getOrder({ id: 42 }, renderOrder) specific — endpoint is settled
getCurrentOrder(renderOrder) most specific — one thing left

Specialize From Your Nearest Relative, Not the Root

getCurrentOrder could have called apiRequest directly, skipping getOrder entirely, with the exact same runtime result. It shouldn't.

Defining getCurrentOrder in terms of getOrder states a real relationship: this is a more specific version of getting an order. Defining it straight off apiRequest only says it's a more specific version of hitting an endpoint, a true but far weaker statement. When one specialized function is really a narrower version of another one you already have, say so by building it from that function, not from the general-purpose one underneath both.

Order Parameters From General to Specific

Building getOrder and getCurrentOrder by hand works, but manually writing a wrapper for every specialization gets old fast. There's a cleaner way coming, and it depends on one thing being true first: the parameters being fixed have to come before the ones still changing.

This series already put that rule to work when gte(threshold) took its threshold before its amount. Here's the real-world payoff of following it: most functional libraries define map as map(fn, array), function first, not map(array, fn) the way most people expect coming from array.map(fn).

That ordering isn't arbitrary. The mapper function is usually the more general, reusable part. The array is the specific piece of data that changes on every call. Put the general part first, and you can fix it once and feed it different arrays over and over. Put the array first, and you'd be stuck re-fixing the data every time just to reuse the same function.

Get the order backwards, and you're stuck reaching for flip to undo it. Get it right from the start, and specializing a function becomes as simple as supplying its first argument and stopping.

That "supplying arguments one at a time and stopping whenever you want" idea has a name of its own, and it's what this series digs into next.