Declarative vs Imperative Programming

  • Home
  • Blog
  • Declarative vs Imperative Programming
Mohammad Azhar Dec 5, 2023 Programming

Introduction

In this blog, we are going to talk about Declarative and Imperative approaches in programming. Declarative programming style is about specifying the “What”, while Imperative programming style is about specifying the “How”.

Let’s illustrate declarative programming with a straightforward example. A prime instance that comes to mind is HTML. When creating a basic “hello world” HTML, you articulate the desired appearance of the page. The rendering engine in a browser interprets this specification, generating a Document Object Model or DOM (In short, a tree of objects used by the browser to represent a web page in the form of a hierarchical set of objects) and displaying it on the screen.

<!DOCTYPE html><html>

<body>

<h1>Hello World!</h1>

</body>

</html>

The above HTML is an example of using a declarative style where the output is declared in the form of an HTML to be interpreted by the rendering engine. In other words, in declarative style, you specify what is required as an output and let the underlying technology (browser rendering engine in this case) or framework execute the details to achieve it.

The above output can also be achieved by using an imperative style using Javascript. Let us take a look at it.

// create a new h1 elemenvar heading = document.createElement(“h1”);

// set the text inside the h1 element to “Hello World!”
var heading_text = document.createTextNode(“Hello World!”);

heading.appendChild(heading_text);

// create a new body element and append the h1 element to it
var body = document.createElement(“body”);

body.appendChild(heading);

// create a new html element and append the body element to it
var html = document.createElement(“html”);

The above is a very simple example and yet we can clearly see that there is a lot more complexity in doing this “imperatively”. We can only imagine the kind of effort it would take if we really had to build complex systems using this approach.

Let’s take another simple example of how an array can be filtered in Javascript using an imperative approach.

const names = [‘Alice’, ‘Bob’, ‘Charlie’, ‘Alex’]

const filteredNames = [];

for (let i=0; i < names.length; i++) {

if (names[i].toLowerCase().startsWith(‘a’)) {

filteredNames.push(names[i]);

}

};

Imperative Style

Given below is the equivalent in declarative style

const filteredNames = names.filter(name => name.toLowerCase().startsWith(‘a’))
Declarative Style

Apart from the obvious difference in the amount of code to be written, there is a more fundamental and important difference. The declarative style only deals with the business logic and not with any of the other details such as how to maintain a variable to hold the result, how to iterate over an array and other lower level details which are not directly related to the business logic.

A declarative programming approach leads to a more maintainable and scalable code. It’s code that is easier to build further upon (scalable) while also being easier to read and debug (maintainable).

Object Oriented or Functional?

When exploring declarative and imperative programming, information often categorizes object-oriented languages as imperative and functional programming languages as declarative, emphasizing their support for higher-order functions (HOFs). While it’s true that HOFs align with declarative principles, it’s essential to recognize that they aren’t the sole means of achieving a declarative style. Even in the absence of HOFs, object-oriented languages like Java can embrace a declarative approach.

Consider Java as an example, especially in scenarios predating SE 8, where lambda expressions were not yet available. In such cases, the java.util.Comparator interface served as a tool for implementing sorting logic. This interface merely necessitates users to define the criteria for determining the greater value when given two values. For instance, let’s explore sorting a list of Person objects in Java based on age, with a secondary criterion of sorting by name if the ages are equal.

import java.util.Comparator

public class PersonComparator implements Comparator {

@Override

public int compare(Person p1, Person p2) {
// Compare ages
int ageDiff = p1.getAge() – p2.getAge();

if (ageDiff != 0) {
return ageDiff;
}

// If ages are equal, compare names
return p1.getName().compareTo(p2.getName());
}
}

List people = new ArrayList<>()people.add(new Person(“Alice”, 25));

people.add(new Person(“Bob”, 25));

people.add(new Person(“Charlie”, 20));

Collections.sort(people, new PersonComparator());

for (Person p : people) {

System.out.println(p.getName() + ” ” + p.getAge());

}

Output:Charlie 20
Alice 25
Bob 25

The aforementioned example embodies a declarative approach, as it exclusively involves defining the comparison logic within a ‘compare’ method, with the actual sorting logic managed by the built-in Collections.sort method.

Alternatively, one could opt for an imperative approach by implementing two nested for loops and iterating over each element, employing a bubble sort algorithm with a complexity of O(n^2). While more efficient algorithms could be implemented, the focus of this example lies not in optimizing the sorting algorithm but in emphasizing that, with a declarative approach, the sorting logic itself can be externalized. This separation allows for the utilization of standard sorting algorithms while keeping your own application code cleaner.

Declarative approaches encourage a reuse of tried and tested libraries and frameworks and a clean separation of concerns. The result is simpler, cleaner and more efficient application code which makes it more maintainable and often better in performance!

Examples of declarative style

Some examples of declarative style of programming are listed below

  1. Front-end frameworks like React, Vue, and Angular advocate for declarative programming by offering tools to define templates and introduce interactivity through hooks, derived state, watchers, and handlers. These frameworks abstract a considerable amount of complexity, enabling the declaration of functions for state management. Users are tasked with defining a template and adjusting the state, leaving the framework responsible for the “How” of managing dependencies between different variables and the DOM updates. This stands in contrast to the approach followed by libraries like jQuery, which necessitate explicit instructions for manipulation tasks.
  2. Frameworks that facilitate declaration are an example of following a declarative approach. Dependency injection serves as another instance of this approach, where the framework takes charge of implementing the method for injecting dependencies, and users simply declare the dependencies they require.
  3. RDBMS and SQL offer a framework for querying data, outlining the structure by specifying tables, relationships, and relevant conditions aligned with your business logic. However, the precise way of storing and retrieving the data are abstracted away and could vary based on the specific implementation of a DBMS.

Conclusion

In summary, there are a lot of advantages of using a declarative approach. It promotes DRY (Don’t Repeat Yourself) code, the use of specialised solutions for common problems and consequentially leads to more efficient, maintainable and scalable code.

However, because declarative programming discourages use of lower level details, this logic is often masked behind the interfaces of frameworks. Though this is essential for writing scalable code, it leads to loss of fine grained control, since you would need to work within the rules of the specific framework or library, although there is often a way to work around this situation for exceptions.

To conclude, declarative programming is a style of programming that ensures a cleaner separation of redundant lower level details and the true business logic. It does not belong to a specific programming paradigm and is available in different kinds of programming languages, frameworks and libraries. It can be thought of as a design principle that must be used wherever possible.