|The Working Programmer's Guide To Language Paradigms|
|Written by Mike James|
|Thursday, 25 May 2017|
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.
The fact that we all use different languages and approaches is an indication that there is no consensus on how best to program.
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 is 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.
The classic is the joke:
A programmer is sent to the store by his wife with the instruction:
"pick up a loaf of bread. If they have eggs, get a dozen".
When he returns with a dozen loaves of bread is wife asks why?
The answer - they had eggs.
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 and this is what "Turing complete" means.
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. It is the difference between having to say
Where the first is a function that has to be told what data to sort and the second is a data object that comes equiped with a verb or method to sort itself.
At this leave the difference is slight and mostly a matter of syntax.
At another it is a powerful step up in abstraction.
Code now attempts 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 - yes Python I'm looking at you.
This is of course a failing of the programmer not the programming langauge.
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.
That is declarative programming is about stating what you want and leaving the system to implement the how.
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 has to be admitted that this isn't how it is usually thought of but it is a shift from procedural to declarative.
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.
|Last Updated ( Thursday, 25 May 2017 )|