Imperative programming is the familiar approach to writing step-by-step instructions for a program to solve some problem. It's the most typical approach for writing programs today. If you're a programmer, you're undoubtedly writing imperative programs at least at some level.
The declarative approach is altogether different and sometimes misunderstood. Or I should say at least that many developers are representing something which, while being more declarative, is not wholly declarative.
They are generally selling functional programming.
Everyone should invest in learning and using functional concepts if only for the profound way it will affect how you think about writing programs. However, writing ClojureScript, Elm, or JavaScript with a functional splash (underscorejs or ramdajs) is not declarative programming. It merely has a declarative leaning.
Programs are not decidedly imperative or declarative, but appear along a spectrum ranging from 0 to 1. The assigned value classifies how well an approach separates policy from intent — more on this in a moment.
SELECT firstname, lastname, email FROM Contacts WHERE state = "PA"
The go-to illustration is usually a T-SQL query. This is a valid starting point. Let's rank it a solid 1 for fully declarative.
Some, however, will also include the following:
var residents = _.filter(contacts, _.matches({state: "PA"}));
This is imperative code because it directly supplies the mechanism for achieving our result (.e.g. filter and matches). There is real value in extracting the imperative bits into functions that compose.
It's definitely a step in the right direction.
Our ultimate goal is to separate how to do it (policy) from what we want (intent). We've not done that. All we've done is abstracted the notion of filtering and matching by stuffing the for loop and if statement inside functions. The for loop and if statement remain, they're just out of sight.
It's obvious that what we want is contacts who reside in Pennsylvania.
Here's the cold, hard truth.
The only declarative program is no program at all.
We need to banish all signs of computation. We need to be left only with an expression of intent. That begs the question: If we do away with the computational part of the program (our functions), how is the program able to accomplish its goal?
Here's the answer: We don't know and we don't care. In fact, it's a great thing that we don't have to know or care.
Having eliminated policy, we are left with intent. That's ultimately all we care about. We want bark commands and have them obeyed. We don't want to concern ourselves with the particulars.
Find them and destroy them! - Agent Smith
How do we express intent without policy?
Messages.
Messages can take many shapes, but they are ultimately just data.
var query = {state: "PA"};
This snippet shows one way of representing our intent. But the truth is, we can design whatever representation we want.
var query = ["state" "PA"];
var query = new Query({filter: "state = 'PA'", order: "lastname"});
A declarative api necessitates data that can be serialized to text. The nature of text is that it can be sent across the wire and interpreted (parsed or deserialized).
That's what's great about T-SQL: it's just text. When the server receives it, it parses the expression into structured data of some sort in order to determine the intent. The same could be done with any potential data representation we devise.
For now, let's just say we transmit the query (what we want) over the wire using JSON.
{state: "PA"}
The server digests it and applies the computation using the same filter and matches functions shown earlier! All we've done is moved the actual computation over the wire — i.e. across a boundary.
var residents = _.filter(contacts, _.matches({state: "PA"}));
This separates policy from intent. Winning!
Just because the server today uses filter and matches doesn't mean it will tomorrow. That's what we want. We want to not care about how the filtering takes place. We just want to make an abstract request. We want the receiving agent, whether in close proximity (a nearby function...) or far away (...on a remote server) to deal with the details.
The wire offers a good constraint. It poses a boundary that forces us to think about how to cross it. This leads us to serialization.
Declarative programming doesn't necessitate a network boundary, but it does need some boundary. Without it we aren't forced to separate policy (how) from intent (what). The boundary forces an intent, however complex, to be fully expressed as data. At the same time, it forces the computation to be hidden away in a separate context.
All requests specify: This is what I want; I don't care how you get it.
The epiphany is this:
Functions cannot be readily sent over the wire.
Functions can close over the environment and hold reference to variables that existed at the point the closure was made. It's this ephemeral program state held by functions that cannot be easily captured and converted to raw text.
That is why data, once married to functions, is no longer declarative. (To illustrate: create a JavaScript object whose keys map to raw or partially applied functions and try to transmit it.) This characteristic of functions is why the boundary between policy (computation) and intent (data) must exist.
Thank you for reading, Padawan. You just leveled up.
Declarative programming in the simplest sense is data-oriented. It represents intent as data. It allows policy (computation) to come later. Thus, only that which can readily be sent over the wire is declarative.
Want to level up again?
Consider the Interpreter Design Pattern or — only after quaffing an Elixir of Monadic Understanding — the Free Monad.
Ponder their declarative properties.
You will attain further enlightenment.
imperative means that you command how the program should do things:
var i = int[5];
for (int x = 0; i < 6; i++) {
i[i] = 0;
}
declarative just means you declare what you want to have
var i = int[]{0...5};
so one time you can be really precise on what should be done on every step and every move
and the other time you can just describe what you want to have without to worry what's going on inside.
one perfekt example for declarative programming languages is SQL you actually never care how the data gets fetched you just say "SELECT * FROM table INNER JOIN table2 ON table1.id = table2.table1_id; "
a perfekt example for imperative languages is C ... you basically have to explain everything step by step to the computer.
This post has been eye-opening to me... I inherently like imperative programming because my mind is obsessed with how things work. It bothers me to have that abstracted away from me.
Sai Kishore Komanduri
Engineering an eGovernance Product | Hashnode Alumnus | I love pixel art
Let's assume that we have an array of numbers —
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]— and we want to extract all the odd numbers in this array.Let's look at a couple of ways through which we can achieve a solution to the above problem, using JavaScript!
Imperative Way
const arrayOfNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const getOddNumbers = (arr) => { const oddNumbers = []; for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 !== 0) oddNumbers.push(arr[i]); } return oddNumbers; }; console.log(getOddNumbers(arrayOfNumbers)); // [1, 3, 5, 7, 9]Declarative Way
const arrayOfNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const getOddNumbers = (arr) => arr.filter(x => x % 2 !== 0); console.log(getOddNumbers(arrayOfNumbers)); // [1, 3, 5, 7, 9]Notice the implementation of
getOddNumbersin both pieces of code. The differences between the imperative way, and the declarative way could be summed, as follows:oddNumbers)There you go, those are the core differences between the imperative, and declarative paradigms of programming.
More Examples
SELECT * FROM MEMBERS WHERE age >= 25Notice, how the "how" is abstracted away from us; and we only declare the "what"!
Further Reading
Hope this helps! :)