This blog post examines how procedural, object-oriented, and functional programming have evolved by compensating for each other’s shortcomings, summarizing the significance of each paradigm for modern development.
As we entered the modern era, computers became indispensable necessities in our lives. Computers possess numerous functions, referred to as programs or applications. The process required to create these programs is programming. Simply put, programming is the act of creating programs. To elaborate further, it is the process by which developers concretely implement the method (algorithm) to make a computer operate as desired. Just as humans need language to communicate with each other, a language is also needed for humans and computers to communicate. This language is called a programming language.
Programming languages began to be used extensively after the invention of the stored-program computer. A stored-program computer, as the name suggests, is a computer where the program is stored in the computer’s memory. Earlier tools, like the abacus or calculator, required manual operation by the user, step by step. In contrast, modern computers use the stored-program method, where programs are stored as files on hard disks and executed.
Programming languages emerged to write programs more efficiently. The earliest language was machine code. Machine code is a language the computer can understand directly, composed of combinations of 0s and 1s. Writing programs directly in machine code today is practically impossible. Above machine code lies assembly language. Assembly language expresses each computer operation as a command, providing instructions such as storing data or moving it to a specific location. While still tailored to the computer and thus difficult to use, its advantage is that it is very fast because the computer understands it easily. For this reason, it is used in a limited capacity in certain fields where speed is critical.
Above that lies the high-level language, designed for human readability. High-level languages use intuitive commands like “add a and b” or “print a.” While easy to understand and program, they require translation into machine code, resulting in relatively slower execution. Common languages like Python, C, and Java all belong to this category.
As programming languages became more convenient for humans, the scope of what could be implemented expanded significantly. While the dramatic performance gains of high-level languages made development easier, it also led to larger program sizes. Therefore, “how to implement it” remains a crucial challenge alongside “what to create.”
The way programming is implemented is heavily influenced by the characteristics of the language used, because each language provides different implementation methods. These differences in implementation methods are called programming paradigms. Representative paradigms include procedural, object-oriented, and functional programming.
Procedural programming executes sequentially from top to bottom. Since commands proceed line by line, it’s easy to grasp the program’s flow and it’s also fast. C, being a machine-friendly low-level language among high-level languages, exemplifies these characteristics well. However, it has a major drawback: as program size increases, maintenance difficulty rises sharply. This weakness can be fatal in modern large-scale software development.
To address this, the paradigm of Object-Oriented Programming (OOP) emerged. As the name suggests, ‘objects’ play a central role in OOP. This paradigm aims to abstractly represent the real world, utilizing the concepts of classes and objects. If a class is a conceptual framework, an object is an instance created based on that framework. For example, when there are objects like “John” and “Michael,” the higher-level concept ‘Human’ that groups them becomes a class.
The maintenance efficiency of object-oriented programming stems from this structure. In procedural programming, if there are many variables with similar characteristics, each must be modified individually. In object-oriented programming, however, modifying only the specific class causes all objects based on that class to reflect the change. Furthermore, classes can be extended through inheritance or composition relationships. For instance, given classes for humans and dogs, one can create an animal class as a higher-level concept encompassing both. By defining common traits like sleep() and eat() for animals, the human and dog classes can inherit and utilize these methods. However, object-oriented programming also has drawbacks: it demands significant time and effort for design, and poor design can necessitate starting over from scratch. Another limitation is that as the number of objects increases, the program’s size grows, potentially leading to decreased performance.
Functional Programming emerged to address the shortcomings of object-oriented programming. Here, the term “function” refers to mathematical functions like f(x)=y. In the existing paradigm, functions could transform input values x internally, meaning consistent results weren’t guaranteed. Functional programming, however, strictly prohibits modifying input values within functions. This restriction leads to more concise and readable code, and crucially, it eliminates side effects at their source, especially in multi-core environments.
Thus, each paradigm has evolved by addressing the limitations of its predecessor. This might raise the question: does a programming language restrict a developer’s way of thinking? However, this is not the case. Developers do not merely follow the features provided by the language; they actively implement new functionality as needed, sharing it as libraries and driving its evolution. When many people begin using these features, they may even be formally incorporated into the language. In other words, the language is not a fixed framework but continuously expands based on user needs. Therefore, there is no need to worry that ‘thinking is constrained by the language’.