๐ง The Purpose of the What Class
๐ง Writing Functions That Read Like Thought — The Purpose of the What
Class
At first glance, the purpose of the What
class may not be immediately obvious. Why should we express a function declaratively by chaining .if()
, .then()
, .else()
methods, rather than simply writing a sequence of if
/else
statements in plain JavaScript?
The answer lies in the ability to decouple the order of declaration from the order of execution. This subtle shift allows the business logic of a function to stand out, while pushing secondary concerns — preparation, validation, error handling — into a separate, orthogonal layer.
๐ฏ Business logic, front and center
Consider the following chain:
business
.if(condA).else(escapeA)
.if(condB).else(escapeB)
.else(escapeC, errorC)
.else(escapeError)
Here, the business logic is prominent. Error handling and early exits are expressed laterally, rather than cluttering the “happy path.”
The business logic itself can be written under the assumption that both condA
and condB
hold true, and that no error has been thrown. Yet this code remains at the top level, without being nested in layers of if
statements or try/catch
blocks. Even though the resulting function is computationally equivalent to a vanilla implementation, it becomes more readable and maintainable.
function businessVanilla(arg) {
try {
try {
// Check condB
if (condB(arg)) {
// Check condA
if (condA(arg)) {
return business(arg);
} else {
return escapeA(arg);
}
} else {
return escapeB(arg);
}
} catch (err) {
if (err.message === errorC) {
return escapeC(arg, err);
}
throw err;
}
} catch (err) {
return escapeError(arg, err);
}
}
We naturally think first about the simplest scenario — the happy path — and only afterwards about exceptions. What
allows us to express code in the same order we form our thoughts.
Declaration ๐ Computation
It’s also worth noting that predicates like condA
and condB
may have side effects on the input object — preparing it for the main calculation.
Computationally, this preparation happens before the core logic. Conceptually, however, it is secondary. Functional programming makes it possible to align the order of declaration with the conceptual order, rather than the raw computational order.
๐ฑ Specialization through derived classes
The benefits of this declarative style become even more apparent when deriving specific classes from What
. In many contexts, neither the preparation logic nor the escape functions need to be hard-coded. Derived classes can provide ready-made methods for both.
⚙️ Example: handling object properties
In a common scenario, a business function takes an object as input. Preparations may involve validating or setting property paths:
const h = new Handler(business)
.checking('user.password', escape) // if 'user.password' is missing → escape
⚙️ Example: handling HTTP requests
Further specializations can even free the developer from writing escape functions.
In HTTP request handling, for example, a lateral escape can be defined with nothing more than an error code and a message. The handler can automatically send the response:
const h = new HttpHandler(business)
.checking('req.user', 401, 'Unauthorized') // enough to implicitely define the escape function
.preparingBody(); // even code (422) and msg ("malformed request") can be omitted
Conclusion
By adopting a declarative approach, we can focus squarely on the business logic of the computation, while delegating preparation and error handling to a clean, orthogonal dimension.
This style not only makes functions easier to read and maintain, but also aligns code with the way we think: start from the straightforward case, then layer in exceptions.
Let’s write code that feels like thought.
— @fizzwiz ✨
Comments
Post a Comment