Out of curiosity and a wish to refresh my memory of some things I learnt in university, I recently started reading about imperative and declarative programming. Before long, I had entered a never-ending cycle of conflicting definitions, opinions and debates. It really is an interesting topic to explore, however there comes a point when you have to consider what is actually useful in your day-to-day work, assuming you are not an academic, but rather a programmer who wants to write good software.
My thoughts on this are as follows.
It is useful to understand the essential differences between imperative and declarative programming, along with the broad definitions of function purity and referential transparency. Why? Because these concepts invite you to open your mind to alternative ways of writing code. Too often, we get attached to a particular way of thinking and solving problems. The more models we have in our toolbox for viewing a software program or system, the more able we are to come up with effective solutions.
As important as understanding these concepts, is understanding some of their relative advantages and disadvantages. This helps us to know what is good and bad about the code we are writing. We do after all want to write good code, so understanding what “good” looks like is therefore obviously important.
Having said all of this, it is counter-productive to get bogged down in the detail and correctness of one definition over another. Keep it practical!
Imperative v Declarative Programming
Broadly speaking, imperative programming is telling the machine what to do, and declarative programming is telling the machine what you want.
A common example provided in C# is the use of a loop to filter a list, as opposed to a LINQ extension method.
So this is imperative:
var myList = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var oddNumbers = new List();
foreach(var n in myList)
{
if(n%2 != 0)
oddNumbers.Add(n);
}
…and this is declarative:
var oddNumbers = myList.Where( n => n % 2 != 0);
In the first example, we are telling the machine what to do:
“Take my list and go through it, and for each item in it if there is a remainder when it is divided by two, then add it to this new list.”
In the second example, we are just saying what we want, not how to get it:
“I want this new list to be a filtered version of my list, containing only items where there is a remainder when divided by 2.”
The key difference here is that in the second example, we are not telling the machine how to filter our list. Under the hood, it may loop through the items as we have specified in our imperative example, or it may prefer some other way of obtaining the same result. We don’t care.
Imperative programming is micro-management. Declarative programming is trust.
In English grammar, an imperative is a command or an order, and a declarative sentence is a statement which conveys information, so you can see where the terminology comes from.
Another way to look at imperative versus declarative programming is that imperative programming changes state, and indeed this is the definition offered by Wikipedia. However, this is where things start to get confusing, as in the above examples neither changes state more than the other, even though this is an accepted answer to the question on stackoverflow.
The answer to this problem stems from the fact that modern software programs tend to be a mixture of imperative and declarative programming.
In isolation, this is a perfectly declarative bit of code:
myList.Where( n => n % 2 != 0);
…however to make it useful, we need to assign it to a variable, which means writing a command, telling the machine to assign our result to a particular location in memory, so in its entirety, this statement is imperative:
var oddNumbers = myList.Where( n => n % 2 != 0);
Yet another way to look at things is that we can have a declarative function call, whose implementation is imperative, and thus we have various levels of abstraction in our programs, from assembly language to application programming.
A feature of pure declarative programming is that it displays referential transparency. This means that functions do not have any hidden side-effects, and that given a specific set of inputs, the outputs will always be the same. In other words, functions are pure.
So here are two more examples of imperative programming:
public int SaveProduct(Product p)
{
_logger.LogSave(p);
return _dataLayer.Save(p);
}
public string GetFileContents(string filePath)
{
return File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");
}
And here is a “pure function”. No side effects, and given any input, the output will always be the same:
public double Square(double n)
{
return n*n;
}
HTML is an example of an entirely declarative language – you describe what you want to see, not how to display it.
As an aside, it is important to recognize that imperative and declarative programming are programming paradigms, and that discussion of programming languages being imperative or declarative can get confusing, as many so-called imperative programming languages can be used in a declarative way and vice-versa.
Which is Best?
Pure functions and referential transparency are good things in that they produce predictable, testable and loosely coupled code. The movement against global variables is testament to the advantages of referential transparency. The modification of global variables represents a side-effect, a change of state, and accessing a global variable represents an unknown input. Dependency injection is also an example of a move towards “purity”. A class becomes more like a pure function when its dependencies are injected into its constructor rather like function parameters, and again this leads to more loosely coupled, predictable and testable code.
So why have imperative programming at all? Why not construct our entire systems around pure, referentially transparent modules?
The difficulty is that our brains like to think in terms of step-by-step processes, and so we find imperative style programs easier to write and easier to read. What results with imperative programs is code which is more likely to contain bugs, but easier to understand for humans.
Purity is a nice ideal, but in reality it is difficult to achieve.
The post Imperative and Declarative Programming appeared first on The Proactive Programmer.