Programming paradigms
Once upon a time, programming languages where extremely different from the ones we have today.
The first-generation programming language was the machine-code one. It was made against the hardware itself, and for most, programming in such way meant creating hardware solutions like moving jumpers or switches or adding/removing cables.
The second generation of programming languages was the assembly language (such as Assembler). The name was related to the assembling stage of these languages into a machine level one that is able to run in the CPU execution pipelines. This language generation was the first made with text, although it was tightly coupled with hardware architectures.
The third generation of programming languages started the age of English-like languages that were able to run on top of hardware specifications with their own instruction set, no longer coupled with the lower hardware level. It started the reusability era.
These languages were not made in real CPU executable code. Programmers did their job in a high-level programming language that, after the compilation stage, translated into a lower-level one that was able to run into the CPU execution pipeline. At the beginning of the high-level programming era, the most diffused languages where IBM ® Fortran and COBOL.
Modern languages, such as .NET, Java, and C/C++, are all of the same generation as their grandparents of the 1950s. Obviously, the current languages have improved features and abilities, because of the long evolution time.
The main differentiation between the previous programming languages and the current ones is the programming paradigm—something like a programming approach of structured methodology that changes the way a programmer creates software.
The oldest one is the imperative programming paradigm. It is made of a direct sequence of steps, usually numbered from 1 to N, that simply executes in a forward-only fashion. In these stages, the ability to jump forward or backward with commands, such as GoTo
, was definitely a killing feature, while now, with modern programming paradigms, it is absolutely avoided. By programming with such an approach, a simple application made to sum two values was a simple sequence of steps, or instructions, that altogether achieved the desired goal. Here is an example in C#:
Console.WriteLine("Step counter: RUNNING"); Console.WriteLine("Write the starting value"); var startingValue = int.Parse(Console.ReadLine()); //value entering starting point REPEAT: Console.WriteLine("Write the ending value"); var endingValue = int.Parse(Console.ReadLine()); if (endingValue <= startingValue) { Console.WriteLine("ending value must be greater than starting value"); goto REPEAT; } //this counter represents the distance between startingValue and endingValue var counter = 0; //increments the counter until needed to reach the endingValue COUNT: if (endingValue > ++counter + startingValue) goto COUNT; Console.WriteLine("You need {0} steps to reach {1} from {2}", counter, endingValue, startingValue); Console.WriteLine("END");
As you can see in the preceding example, the whole program is only a list of steps where the current actor is at once the computer asking for something on the console and the user writing some response on the console. The program is unable to do multiple things together. Either the computer places a question to the user, or the user enters a digit or something on the keyboard to give the computer a command or some data.
Note
C# is a general purpose programming language supporting imperative, procedural, declarative, object-oriented, component-oriented, service-oriented, and functional programming paradigms all together.
There are no interaction constructs (for
, for...each
) available in imperative programming, and there is no code factorization into subroutines able to abstract and make reusable single portions of code.
The check logic against the user value is available through a simple GoTo
statement that is able to move the control's flow pointer (the actual execution row) to a newly desired position. Regarding this check logic, the execution flow simply goes back to some previous line executing and entering a destination value.
Similarly, when it is time to count the distance between the two given values, the logic again uses the GoTo
statement, changing the current state of the counter
variable to the updated value. It is the last time the variable will contain the required result.
Note
It is interesting that throughout the imperative programming paradigm, C# is even faster than when programming in an object-oriented one, because of the higher usage of the stack memory, instead of the heap memory that is slower.
If you are interested in code optimization and high performance programming, you may find it interesting to read my other book Learning .NET High Performance Programming.
In imperative programming languages, such as Fortran, Pascal, or COBOL, there was a number for each row to give developers the ability to create interactive constructs, such as recurring jobs or any other interaction logic, by jumping between rows.
Obviously, this choice is not available in modern general-purpose languages, such as C#, which give the same feature with the use of a label (in the preceding example, we used Repeat
and Count
) instead of the row number. The result is the same.
To understand imperative programming, we must understand the meaning of the state. An application's state is the sum of all the data usually available within the code through fields, properties, and variables. When we started the preceding code, the application state was empty (we will ignore the Common Language Runtime (CLR) stuff that is in the memory together with our data). During the execution, some variables (startingValue
and endingValue
) became available by asking data from the user.
Later, a counter
variable will make the most of the work to find the distance between the two numbers. What is the heart of the imperative programming paradigm is the status change that the code makes against available variables. As visible, the counter
variable becomes incremented until the wanted value is found.
This status change still happens in other paradigms such as procedural-programming or object-oriented programming, although these paradigms bring to a higher level abstraction of data structures or a better code reusability.