We’ve read and heard of so many programming paradigms in books and speeches. Today, an experienced programmer is pretty familiar with procedural, functional, object-oriented, and even multi-paradigm programming languages. But, less is taught about how and when these paradigms must be utilized in our day-to-day development career.
Softwares are made of several interacting elements. Whatever we call these elements (functions, classes, structures, etc.), they fall into the same categories.
It’s remarkably hard to think about a programmer who doesn’t own a copy of an algorithms textbook. Programming is all about algorithms, right? At least, this is what we were taught. Our first experiences in programming generally started by implementing one or more classic algorithms.
Algorithms are fundamental to our programs. They can be as simple as finding an item within a list or as intricate as computing a spanning tree of a graph. However, in nature, algorithms don’t care much about specific data types. That’s why we can express them in pseudo-codes or implement them using templates/generics.
Where algorithms come, data structures follow. However, in academic sense, data structures are more like collections of similar (or polymorphic) items. These collections are typically classified by their internal structure: ordered (arrays) v.s. unordered (sets), linear (linked-lists) v.s. non-linear (trees), etc.
Regarding data structures, algorithms are here to extract information from, mutate or transform them. Normally, data structures can also be implemented using templates/generics.
I’ve deliberately separated composite types from previous data structures. Because despite collections, they can be composed of several distinct data types.
There are two known composite types: discriminated unions (or sum types) and records (or product types). We sometimes call composite objects “data objects,” since they are typically used to hold data or transmit it. Data objects have public attributes and no methods. Otherwise, they must be considered as models.
Models are composite objects with behaviors (or methods). Models usually represent real-world entities, but in abstractly; a staff in a company, a shopping order, and an aircraft in a flight simulator are examples of models.
We describe models by their attributes, but they can also act or be acted on by calling methods or sending messages.
By processes, I don’t mean processes and threads in an operating system, but a series of sequential or non-sequential steps we perform to fulfill a task. UNIX tools (or commands) are great examples of such processes.
Processes do their tasks by combining one or more algorithms and several data structures. Compilers are also good examples of processes with distinguishable steps: tokenizer, parser, optimizer and code generator. Each middle step takes its input from the previous one and feeds the next.
Large software systems contain multiple processes. These processes have lifecycles and can communicate as well as what models can do.
Object-oriented, functional, and procedural programming are the three major paradigms in use today. Object-orientation is, without doubt, the dominant paradigm, while the functional style is slightly catching up. But, procedural programming is slowly fading away.
The effectiveness of each programming language and its paradigm is about how well-suited it is to express the previous constructs. Though obviously, not all of the constructs can be effectively represented by a single structure. For example, one of the main flaws of pure-OO languages is to call and express those solely as classes, albeit algorithms and data objects essentially do not require object-oriented features like encapsulation and inheritance. On the other hand, functional programming gets it right for the most part, except for data structures that are mutable in nature — when we’re adding an item or removing it, we’re actually modifying the same collection rather than creating a new one.
My point here is not to compare programming paradigms, but to emphasize that each paradigm is convenient for specific problems:
- procedural programming has been in practice for a long time to implement algorithms and mutable data structures, and it’s been very successful;
- functional programming is perfect at expressing immutability and implementing processes;
- finally, object-oriented programming shines well at modeling objects and simulating their behaviors.
So, in conclusion, we not only require multi-paradigm programming languages, but also we should learn how to utilize each paradigm in its proper place.