The Art of Turning an Imperative Function Into a Declarative One

Guillaume Pichot
4 min readJul 16, 2021
Photo by Wes Carpani on Unsplash

Implementing software using a programming language is an art equivalent to writing a novel as it requires practice and experience to make the best use of a language in order to write a compelling story or program.

There are two main programing paradigms that a developer can use when writing a program:

  • imperative (procedural, object-oriented)
  • declarative (functional, reactive)

Each paradigm is valid as the same thing can be achieved in many ways and styles when programming. The difference is that a paradigm suits a language better than others and that some languages are more expressive than others. This means that adopting the best suited paradigm to a language when writing software will bring the greatest value towards the quality of the code produced for running such software. The achieved quality will be critical to maintain, evolve and change an implementation as it enables a human being to reason about it by lowering its cognitive complexity.

When writing a program by adopting an imperative approach such as procedural code, a developer takes on the responsibility of implementing not only the logic required for this program but also of ensuring that a program is safe to run and handles edge cases and unexpected issues seamlessly. This results in writing additional defence mechanisms as to protect a program from failing which result in additional complexity and more code to maintain.

In this article, I will take as an example a procedure that performs a “complex” logic and identify how to modify this procedure by adopting a more declarative style of programming. I will also use JavaScript as the language for the implementation.

And here it is, a trully magnificent procedure:

function transformStringOfTokensIntoArrayOfUpperCaseTokens (stringOfTokens, separator) {  let result = [];
if (stringOfTokens !== undefined && separator !== undefined) {
if (stringOfTokens === '') {
return result;
} else {
const tokens = stringOfTokens.split(separator);
for (let i = 0; i < tokens.length; i++) {
result.push(tokens[i].toUpperCase());
}
return result;
}
} else {
return result;
}
};

Let’s try to understand what this procedure does and let’s step in the shoes of a developer who would read it for the first time.

Looking at it, this procedure is implemented as a function in Javascript. It seems to be a rather old way of writing a function and does not use the more modern syntax supported by the language. From reading its name, it can be used to split a string with a given separator into an array of upper case tokens. But what does it do exactly?

Let’s see what the pseudo code for the logic look like:

- Define the default result of the function to be an empty array
- Verify that the parameters are defined otherwise return the default result
- If the string parameter is empty return the default result
- Otherwise split the string using the separator into an array of token
- Upper case each token and add them to the result
- Return the result that contains the upper case token in the array

This function does a lot of things in order to ensure that it is safe to execute and produce the expected result. This means that the developer had to implement all of these checks and the actual transformation logic as to guarantee that the procedure will not fail even when processing edge cases.

But is this what was really required? All this complexity for turning a string into an array of upper case tokens?

How could we describe the transformation that is required instead in a succession of logical steps?

Maybe it could look like this:

> From a given string and given a separator
| Split the string using the separator into an array
| Then upper case each item in the array
| Then return the array

OK, that’s interesting, now the same logic is represented as a succession of steps where each step takes an input and yields a result. But how could we implement this?

const transformStringOfTokensIntoArrayOfUpperCaseTokens = (stringOfTokens = '', separator = '') => {

return stringOfTokens.split(separator)
.map((item) => item.toUpperCase);
};

Is that it really?

So what has changed with this new implementation?

Looking at it, although this implementation achieves exactly the same procedure as the previous one, there are fundamental differences to it.

It uses a more modern syntax and uses additional capability of the language like defining a default value on the parameters in order to handle edge cases.

It also use a more declarative syntax that describe the processing that needs to be performed on the input which greatly reduces the cognitive complexity of the implementation. Indeed, each step is clearly defined and quite easy to understand as it only does one thing.

In summary, implementing a procedure that performs complex operations for transforming an input to a result is all about understanding the logical steps of the transformation and orchestrating these steps into a succession of small operations where each operation yields its result to the next one.

--

--

Guillaume Pichot

Engineering leader and agile enthusiast with a passion for Software Craftsmanship, but more importantly, a loving Father and Husband.