Imperative vs Declarative

Wikipedia defines imperative and declarative programming as:

imperative programming is a style that uses statements that change a program’s state … focuses on describing HOW a program operates.

declarative programming is a style that expresses the logic of a computation without describing its control flow … focuses on describing WHAT the program should accomplish in terms of the problem domain.

When we read the definition of something, we tend to focus on terms that are already familiar to us, and then use those terms as a metaphor in order to understand the definition. Here in this case, we should think that, for short, imperative programming is about HOW whereas declarative one is about WHAT. Of course, those definitions are correct, but somehow confused and thus difficult to understand. How can we write a program without describing HOW? Finally, the computer must do the job and it must know exactly HOW to do. It turns out that the WHAT here is still a little of HOW, but in a different way.

So every time I hear someone saying “imperative is HOW and declarative is WHAT”, I feel disappoint. Rather, I like to hear: “imperative code describes control flow whereas declarative one does not”. But what is control flow?

Again, Wikipedia defines control flow correctly and clearly:

control flow is the order in which INDIVIDUAL statements, instructions or function calls are executed

The word INDIVIDUAL is important. Let’s look into a typical control flow code:

if (thisIsTrue) doThis;
else doThat;
while (thisIsTrue) {

The code contains multiple INDIVIDUAL steps – statements, instructions, function calls – which are DISCRETE and separated by the semicolons. Because these steps are DISCRETE, each of them must change program’s state in order to contribute to the whole program; otherwise, that step is meaningless. And assignments is the most popular way to change program’s state. The doThis, doThat in the code above can be one of following statements:

int x = 0;
x = 1;
x += 2;
x = x + 3;
x = foo();

As you can see, every statement without assignments (or their variants) is meaningless. Event the call to the void function bar() still changes program’s state since there must be at least one assignment inside bar().

Yes, this is imperative programming where assignments and semicolons are dominant.

Declarative programming, on the other hands, does not have assignments, and hence does not need semicolons to separate steps, since there is no any step, no state change, no side-effect [1]. In declarative world, everything is declared by its definition. To achieve a task, we have to specify and define its outcome (yeah, it is WHAT). For example, to check whether an integer number is prime, we need a definition and sub-definitions [2] if needed:

  • A prime number N is an integer number that is greater than 1, and does not have any divisor from 2 to N-1.
  • A number N that does not have any divisor from 2 to N-1 is … well, let’s see the code :)

Translating to code:

    N > 1 && noDivisor(2, N)

noDivisor(from, N)
    from >= N || (N % from != 0 && noDivisor(from + 1, N))

Writing declarative code requires a very different thinking. The most challenge of converting imperative code into declarative one is eliminating loops because only loops really need state change. Indeed, without state change, the break conditions of loops will never be met, and loops that aggregate something are not possible.

In many cases, if a loop is about going through a list, most declarative languages provide some built-in features such as map, reduce, filter, for-each with lambda expressions that are quite useful and succinct, since the HOW – how to go through the list – is left up to the language’s implementation. But in general cases, the only way to remove loops, of course, is recursion. Unfortunately, not every language is optimized for recursion.

I could not say, imperative vs declarative, which one is better. However, in my observation, declarative code is usually more elegant and really well suited for parallel programming.

[1] Saying “no state change, no side-effect” is not really true in some cases. Indeed, even printToConsole() or writeToFile() functions could cause side-effect. The word “no” should be understood as “no or minimize

[2] This is Referential Transparency. It simply means that: “in any given sentence, you can replace the words in that sentence with their definitions, and not change the meaning of the sentence” said Uncle Bob in his excellent article.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s