Page 1 of 3
You would think that we would agree on how best to program. In fact we are still warring tribes trying to make the case for our own particular view of how programming should be done. The question is:
How should we program?
It's not a difficult question to ask - but to answer it is another matter.
Of course you might believe that you have the answer. You might believe that the language you are most familiar with and its approach is the solution. But consider this. If there was a "best" way to program we would all be using it rather than the rag bag of bits and pieces that make up the modern language.
It would be nice to say that we are where we are, using the languages that we use and holding the views on what is good and bad by design, by careful thought and a lot of theory, but this would be wrong.
To make it clear from the very start there is no single theory of how we should program that is provably better than the rest.
What are clear are the objectives of a good language.
A good language should be easy to use, should make it almost impossible to introduce bugs and should create programs that are easy to understand and easy to maintain.
When expressed in this Utopian manner it is immediately clear why these objectives are not achievable, only approachable.
Consider for a moment the most intelligent and long tested programming system of all - tell another human to do the something.
Even with the most intelligent computer on the planet (allegedly) and a flexible natural language honed over thousands if not tens of thousands of years things still go wrong. Yes natural language is great at conveying subtle meaning, poetry even but as a way of specifying a task it tends to leave much unsaid. And in the space of assumption there is a lot of freedom to make mistakes.
So what are the possible approaches to designing a programming language?
What are the possibilities?
Even here it is difficult to be precise as a given language can take a little from each of the key approaches and so be a blend and not fit into a single category.
However we can identify two broad strategies:
Procedural v Declarative
The split between the procedural and the declarative is really the big dividing line at the moment and it represents two distinct ways that programming could go.
Let's look at each in turn.
Procedural, or imperative, programming was the way we first approached programming a machine because it corresponds most closely to the way the machine works.
A computer takes one instruction at a time and carries it out or "executes" it.
A program is just a list of executable instructions and a program overall gets the machine to perform a procedure - adding two numbers together say.
The reason it is often called "imperative" programming is that an imperative is a command like "run!" or "stop!" hence imperative programming is about putting together lists of commands.
Essentially the procedural approach to programming is all about telling the machine what you want it to do in minute detail
add 1 to 3 and display the result
is a procedural program in English.
Over the years the procedural approach has become increasingly sophisticated and abstract.
Starting from the low level machine instructions we have worked our way up to bigger and bigger instructions that achieve more and abstract away from the machine's hardware.
Early procedural languages were about the machines they ran on - later languages were and are more about the problems we are trying to solve.
The key idea here is the transformation of a static text into a dynamic process. This is what makes procedural programming difficult and why it is a skill worth having even if you are not going to become a programmer.
To capture the essence of any process all you need are three ingedients in a language. You need the default sequencing of commands - that is read and obey one command after another. You need some way of making a decision about which commands to carryout - that is you in some sort of If statement. And finally you need a way to repeat commands - you can opt for a loop or recursion they are equally good.
Any language that has these three features is well on its way to being "Turing complete" - a term which basically means that it can be used to compute anything that can be computed. A better way of putting it is that the language is capable of expressing any algorithm. Its static text is enough to represent and generate any dynamic process.
When you think about it the bar for being Turing complete is set fairly low but it is important to remember that this is what makes a programming language a programming language.
The main process in the development of procedural languages has been the construction of a hierarchy of procedures or subroutines which eventually become formalised into language commands.
So back in the early days you would have constructed a sort program using registers and low level branches, then you would have organised this into a sort subroutine which could be called and then on to a sort command embedded in the language.
In most cases today the step to include the new command in the language is never taken because its more efficient and flexible to simply create a standard framework consisting of a function library.
The big breakthrough in procedural languages was the invention of object oriented programming.
This allowed programmers to group data and procedures into a single package - the object. At one level this is just a practical convenience, keeping code with the data it is going to operate on. At another it is a powerful step up in abstraction.
Code now attempt to mimic the behaviour of objects in the real world. Chunks of software can have states and behaviours just like real objects.
Today the situation is that you can have procedural programming without objects but its generally considered to be a regrettable return to old and inefficient ways.
Even so many object oriented procedural languages are used as if they were first generation languages and objects are simply ignored or used in a simple non-object way.
You can provide the tools but you can't make a programmer use them.
Procedural programming seems to natural and obvious that it is difficult to see what alternative there is.
What makes programming hard is the way that a static text has to represent a dynamic process composed of sequenced commands, decisions and repeats. Obviously to make it easier all you have to do is remove all that pointless dynamic behaviour - get rid of the loops and decisions.
Declarative programing is based on the idea that rather than specifying exactly how a problem should be solved you simply state the problem and perhaps the objective and let the system figure out how to achieve the result i.e what procedure is necessary to give the objective from the problem.
Put like this declarative programming sounds as natural and as obvious as procedural.
For example, if you give a task to a human, i.e. you want to program them, you can either break the task down into the steps needed to solve the problem or you can simply state the problem and let the person sort out how to solve it.
This example also indicates that there is a continuum between procedural and declarative that depends on how much of the solution you provide and how much you expect the computing agent to provide. It's all a question of what you assume the agent knows. Another way of looking at this is to think of the shift from procedural to declarative as a moving of the "intelligence" from the program to the agent.
For example, in the early days we would have written a sort program in minute detail specifying the exact sorting algorithm to be used. Today we probably just use a sort procedure supplied as part of the language framework and leave the method used up to the procedure.
It is important to note that this interpretation isn't the one most often used today because declarative approaches to languages tend to attempt to banish all notion of procedure from what is going on and not just cover it up or encapsulate it in a black box procedure.
Creating a perfect declarative approach is difficult and in reality what we have are moves in the declarative direction. We attempt to build languages that remove aspects of the dynamic process expressed by a procedural program text.
To see what this is all about we need to look at some specific examples of declarative systems.
The Procedural v Declarative UI
It is also worth knowing that the procedural/declarative split is also used in a lesser sense when applied to situation like UI construction.
Markup and object creation initialization languages like XAML or MXML are called declarative because you write a specification of a UI using them and don't care about how the objects representing the UI are created.
The procedural approach is to call object constructors in a given order and initialize properties explicitly. For example, you might create a button by creating a Button object:
Button button1=new Button();
The declarative approach would be to use a markup language something like:
If you think about this for a moment you will realise that this distinction really is trivial - in theory at least. All that happens to the markup language is that it is used to create the objects and set the properties that the procedural code explicitly created. You can see that there is some loss of procedure in going to the declarative form but not much because there are no loops or decisions used.
The real reason for using object creation and initialisation languages is that they provide a language neutral way of creating a UI which makes the implementation of graphical designers easier.
In other words, markup/object creation languages v procedural languages is not a deep issue.
To see what the issue really is you have to take a look at the two best known approaches to declarative languages - functional and logical languages.