Comparison of programming paradigms
This article attempts to set out the various similarities and differences between the various programming paradigms as a summary in both graphical and tabular format with links to the separate discussions concerning these similarities and differences in existing Wikipedia articles
Main paradigm approaches
The following are considered the main programming paradigms. There is inevitably some overlap in these non mutually-exclusive paradigms but the main features or identifiable differences are summarized in the following table:
- Imperative programming - describes computation in terms of statements that change a program state
- Functional programming - treats computation as the evaluation of mathematical functions and avoids state and mutable data.
- Procedural programming / structured programming - specifying the steps the program must take to reach the desired state
- Event-driven programming - the flow of the program is determined by events—i.e., sensor outputs or user actions (mouse clicks, key presses) or messages from other programs or threads.
- Object oriented programming (OOP) - uses "objects" – data structures consisting of datafields and methods together with their interactions – to design applications and computer programs.
- Declarative programming - expresses the logic of a computation without describing its control flow
- Automata-based programming - in which the program or its part is thought of as a model of a finite state machine or any other formal automata.
None of the main programming paradigms have a precise, globally unanimous definition, let alone an official international standard. Nor is there any agreement on which paradigm constitutes the best approach to developing software. The subroutines that actually implement OOP methods might be ultimately coded in an imperative, functional or procedural style that might, or might not, directly alter state on behalf of the invoking program.
Paradigm |
Description |
Main characteristics (examples) |
Related paradigm(s) |
Critics? |
|---|---|---|---|---|
Imperative |
Computation in terms of statements that directly change a program state (datafields) |
Direct assignments, Common data structures, Global variables |
Edsger W. Dijkstra, Michael A. Jackson |
|
Structured |
A style of Imperative programming with more logical program structure |
Structograms, Indentation, absence of GOTO statements |
Imperative |
|
Functional |
Treats computation as the evaluation of mathematical functions avoiding state and mutable data. |
Lambda calculus,Compositionality, Formula, Referential transparency, no side effects |
||
Procedural |
Derived from structured programming, based upon the concept of modular programming or the procedure call |
sequence, selection, and iteration and modularization |
Structured, Imperative |
|
Event-driven including time driven |
Program flow is mainly determined by events (such as mouse clicks or interrupts including timer) |
Main loop, Event handlers, Asynchronous processes |
Procedural |
|
Object-oriented |
Treats datafields as "objects" manipulated only through pre-defined methods |
Objects, Methods, Message passing, Information hiding, Data abstraction, Encapsulation, Polymorphism, and Inheritance |
See here and |
|
Declarative |
Expresses the logic of a computation without describing its detailed control flow |
(4GLs, Spreadsheets, Report program generators) |
||
Automata-based programming |
The program is thought of as a model of a finite state machine or any other formal automata. |
State enumeration, Control variable, Changes in state, Isomorphism, State transition table |
Imperative, Event-driven |
|
Paradigm |
Description |
Main characteristics (examples) |
Related paradigm(s) |
Critics? |
Differences in terminology
Despite multiple (types of) programming paradigms existing in parallel (with sometimes apparently conflicting definitions), many of the underlying fundamental components remain more or less the same (constants, variables, datafields, subroutines, Calls etc) and must somehow therefore inevitably be incorporated into each separate paradigm with equally similar attributes or functions. The table below is not intended as a guide to precise similarities, but more an index of where to look for more information - based on the different naming of these entities - within each paradigm. Non-standardized implementations of each paradigm in numerous programming languages further complicate the overall picture, especially those languages that support multiple paradigms, each with its own jargon
Entity |
Imperative |
Structured |
Functional |
Procedural |
Event-driven |
Object-oriented |
Declarative |
Automata-based |
|---|---|---|---|---|---|---|---|---|
Subroutine |
Subroutine |
Subroutine |
Function |
Procedure |
Event handler |
Method |
Script? |
|
Data structure |
Data structure |
Data structure |
Data structure |
Data structure |
Data structure |
Data structure |
||
Record |
Record |
Record |
Record |
Record |
Record |
Object composition, Object aggregation |
||
generic code |
generic code |
generic code |
parametric polymorphism |
generic code |
generic code |
type polymorphism |
||
field |
field |
field |
field |
field |
field |
data member |
||
data item |
data item |
data item |
data item |
data item |
data item |
Object |
||
constant |
constant |
constant |
constant |
constant |
constant |
Immutable object |
||
variable |
variable |
variable |
name binding |
variable |
variable |
Mutable object |
||
Initialization code |
Initialization code |
Initialization code |
Initialization code |
Initialization code |
Initialization code |
Constructor (subroutine) |
||
Call |
Call |
Procedure call |
Function call |
Procedure call |
Event |
Message passing |
||
default subtype? |
inheritance |
|||||||
Entity |
Imperative |
Structured |
Functional |
Procedural |
Event-driven |
Object-oriented |
Declarative |
Automata-based |
Support for new paradigms, supporting languages & syntactic sugar
Syntactic sugar is a term used to describe the sweetening of program functionality by the introduction of "new features" together with its new terminology. One example of syntactic sugar may arguably be classes in C++ (as well as in Java, C#, etc.). The C programming language is fully capable of object-oriented programming using its powerful facilities of function pointers, type casting, and structures. However, it is claimed that languages such as C++ make object-oriented programming more convenient by introducing syntax specific to this coding style. Moreover, the specialized syntax works to emphasize the object-oriented approach to new programmers. Another example of syntactic sugar may arguably be functions and looping syntax in C (as well as other procedural and structured programming languages). Assembly language is fully capable of procedural or structured programming using its powerful facilities for modifying register values and branching execution depending on program state. However, it is claimed that languages such as C make procedural and structured programming more convenient by introducing syntax specific to these coding styles. Moreover, the specialized syntax works to emphasize the structured approach to new programmers. Features of the C# (C Sharp) programing language, such as properties and interfaces, similarly do not enable new functionality, but instead (it is claimed), make good programming practices more prominent and more natural.
Some programmers feel that these features are either unimportant or outright frivolous. For example, Alan Perlis once quipped, in a reference to bracket-delimited languages, that "syntactic sugar causes cancer of the semicolon" (see Epigrams on Programming).
An extension of this is the term "syntactic saccharin", meaning gratuitous syntax which does not actually make programming easier.
Performance comparison
Purely in terms of total instruction path length, a program coded in an imperative style, without using any subroutines at all would have the lowest count. However, the binary size of such a program might be larger than the same program coded using subroutines (as in functional and procedural programming) and would reference more ""non-local" physical instructions that may increase cache misses and increase instruction fetch overhead in modern processors.
The paradigms that use subroutines extensively (including functional, procedural and object oriented) and do not also use significant inlining (via compiler optimizations) will, consequently, use a greater percentage of total resources on the subroutine linkages themselves. Object oriented programs - that deliberately do not alter program state directly - instead using methods to encapsulate these state changes - will, as a direct consequence, have a greater overhead (since message passing is essentially a subroutine call - but with two more additional overheads; parameter copying and dynamic dispatch). Copying parameters for message passing may involve significant resources that far exceed those required for the state change itself.
Pseudocode examples comparing various paradigms
A pseudocode comparison of imperative, procedural, and object oriented approaches used to calculate the area of a circle (πr2. ), assuming no subroutine inlining, no macro preprocessors, register arithmetic and weighting each instruction 'step' as just 1 instruction - as a crude measure of instruction path length - is presented below. The instruction step that is conceptually performing the actual state change is highlighted in bold typeface in each case. Note that the actual arithmetic operations used to compute the area of the circle are the same in all three paradigms, with the difference being that the procedural and object-oriented paradigms wrap those operations in a subroutine call that makes the computation general and reusable. The same effect could be achieved in a purely imperative program using a macro preprocessor at the cost of increased program size. Conversely, subroutine inlining by a compiler could reduce procedural programs to something similar in size to the purely imperative code. However, for object-oriented programs, even with inlining, messages still have to be built (from copies of the arguments) for processing by the object oriented methods. The overhead of calls, virtual or otherwise, is not dominated by the the control flow alteration itself - but by the surrounding calling convention costs, like prologue and epilogue code, stack setup and argument passing,.
Imperative |
Procedural |
Object-oriented |
|---|---|---|
|
|
|
The advantages of procedural abstraction and object-oriented-style polymorphism are not well illustrated by a small example like the one above. This example is designed principally to illustrate some intrinsic performance differences, not abstraction or code re-use.
Subroutine/Method call overhead
The presence of a (called) subroutine in a program contributes nothing extra to the functionality of the program regardless of paradigm, but may contribute greatly to the structuring and generality of the program, making it much easier to write, modify, and extend. The extent to which different paradigms utilize subroutines (and their consequent memory requirements) influences the overall performance of the complete algorithm, although as Guy Steele pointed out in a 1977 paper that a well-designed programming language implementation can have very low overheads for procedural abstraction (but laments, in most implementations, that they seldom acheive this in practice - being "rather thoughtless or careless in this regard"). In the same paper, Steele also makes a considered case for automata-based programming (utilizing procedure calls with tail recursion) and concludes that "we should have a healthy respect for procedure calls" (because they are powerful) but "use them sparingly"
In terms of the frequency of subroutine calls:-
- for procedural programming, the granularity of the code is largely determined by the number of discrete procedures or modules.
- for functional programming, frequent calls to library subroutines are commonplace (but may be frequently inlined by the optimizing compiler)
- for object oriented programming, the number of method calls invoked is also partly determined by the granularity of the data structures and may therefore include many "read-only" accesses to low level objects that are encapsulated (and therefore accessible in no other, more direct, way). Since increased granularity is a prerequisite for greater code reuse, the tendency is towards fine-grained data structures, and a corresponding increase in the number of discrete objects (and their methods) and, consequently, subroutine calls. The creation of "god objects" is actively discouraged. Constructors also add to the count as they are also subroutine calls (unless they are inlined). Performance problems caused by excessive granularity may not become apparent until Scalability becomes an issue.
- for other paradigms, where a mixture of the above paradigms may be employed, subroutine usage is less predictable.
See also
- Comparison of programming languages
- Comparison of programming languages (basic instructions)
- Granularity
- Message passing
- Subroutine
External links
- Comparing Programming Paradigms by Dr Rachel Harrison and Mr Lins Samaraweera
- Comparing Programming Paradigms: an Evaluation of Functional and Object-Oriented Programs by Harrison, R., Samaraweera, L. G., Dobie, M. R. and Lewis, P. H. (1996) pp. 247–254. ISSN 0268-6961
- "The principal programming paradigms" By Peter Van Roy
- "Concepts, Techniques, and Models of Computer Programming"(2004) by Peter Van Roy & Seif Haridi, ISBN 0-262-22069-5
- The True Cost of Calls- from "Harder, Better, Faster, Stronger" blog by computer scientist Steven Pigeon