I've published over 30 articles this past year, with that comes a lot of research and I've seen so many different programming paradigms that I just have to write about it.
But before we begin, we first got to know what is a programming paradigm.
Table of Contents
Programming Paradigms
As developers our number one enemy is complexity.
I'm sure you've heard the term, "If I had more time, I would've made the program simpler".
Because simpler code is more maintainable and can be easily debugged.
In a nutshell:
Complexity is the enemy
To battle complexity, software developers have created different programming paradigms or styles of programming, similar to martial arts. It's not bound to a specific programming language, but some languages are more suited to one style.
Let's go over the history of these paradigms.
The Dark Ages
The earliest known programming paradigm was called non-structured. It was barbaric due to the use of goto statements to literally jump to different areas of the code.
It got the job done, but at what cost?
Unreadable or spaghetti code.
Many people complained about the brutality of non-structured programming, it was horrible to maintain and debug.
Until one day, a man by the name of Edsger W. Dijkstra advocated against using goto or equivalent.
Thus creating his own style of programming called structured programming and ending the dark ages of non-structured.
Structured Programming
The school of structured programming brought many successes.
Computer programs had more clarity, quality, and were made faster than ever.
Structured programming advocated the use of structured control flow such as:
-
Conditionals —
if/then/else
-
Repetition —
while/for
- Block Structures
- Functions
They also completely rejected the use of dark arts, such as goto or its equivalent.
Structured programming was the default, and many new programming languages were created with structured programming in mind.
Eventually, structured programming advanced so much that it was too divided into two new styles.
Imperative Programming
The first of these new styles was imperative programming.
Imperative programming is a programming paradigm that uses statements to change the programs state. It focuses on the HOW by showing you the solution step by step.
For example, let's say we were tasked to return even numbers from a list of integers.
Imperatively we would solve it like this:
const list = [1,2,3,4,5,6];
const result = [];
for(let i = 0; i < list.length; i++){
if(list[i] % 2 == 0){
result[] = list[i]
}
}
Here we know exactly what's happening, we can simply follow the code, and see how the solution works.
Within imperative programming, there are also another two inner-schools.
Procedural Programming
Procedural programming is a subset of imperative programming where we split the step by step instructions into procedures.
But what is a procedure?
A procedure is basically a function that DOESN'T return any value, and ACHIEVES some sort of side effect.
A good example would be the classic loop in most programming languages.
const list = [1,2,3,4,5,6];
const sum = 0;
for(let i = 0; i < list.length; i++){
sum += list[i]
}
It's essentially a function that doesn't return anything but causes side effects.
Benefits of Procedural Programming:
- Excellent for general-purpose programming
- Because of the simplicity of the code, it's easier to implement compilers and interpreters
- Simplicity
- Ability to be highly modular
- The memory requirement is slow, making it fast and efficient
Languages that support Procedural Programming:
- C
- C++
- Java
- Javascript
- Python
Object-Oriented Programming
Object-Orientated Programming is a subset of imperative programming where you structure your code in objects that can hold data (in form of fields) and code (in form of methods).
Object-Oriented Programming is by far one of the most popular and widely used programming paradigms in our times.
There are four main principles in Object Orientated Programming:
- Encapsulation - Binds data and it's related methods together within a class. It also protects the data by making fields private and giving access to them only through their related methods.
- Abstraction - It's the concept of object-oriented programming that "shows" only essential attributes and "hides" unnecessary information.
- Inheritance - It's the mechanism of basing an object or class upon another object or class, retaining similar implementation.
- Polymorphism - It's the ability of an object to take on many forms.
I have another article, with an in-depth explanation for each of these principles.
You can read it here.
OOP Principles For Dummies
Tamerlan Gudabayev ・ Mar 21 '21
Benefits of Object Orientated Programming:
- Reusability due to inheritance
- Flexibility due to Polymorphism
- Security due to Encapsulation and Abstraction
- Lower costs due to reusability
Languages that support Object-Oriented Programming:
- Java
- C++
- Python
- PHP
Declarative Programming
On the other hand of the spectrum we have declarative programming, it also comes from structured programming but unlike imperative programming where it focuses on the HOW, declarative programming focuses on the WHAT.
An example of a declarative programming language would be SQL (Structured Query Language).
So let's say for example you want to get all rows from a table called users
SELECT * FROM users
You don't really care how SELECT
works, you simply want to get all users.
Similar to imperative programming, declarative programming also has four inner schools.
Logic Programming
Logic Programming is a programming paradigm based on formal logic. Meaning that you have a bunch of facts/statements, and you get new statements that comply with the original facts.
A classical example would be:
- Socrates is a Man
- All Men Are Mortal
From these two statements, we can conclude that Socrates is also mortal.
But how is this expressed in code?
Take a look at an example made in Prolog:
man(Socrates).
mortal(X) :- man(X).
?- mortal(Socrates).
Let's break this down:
- The first line expresses that Socrates is a man.
- The second line says "X is mortal if X is a man".
- The third line is our question that says "Is Socrates mortal?" which will return
yes
.
Benefits of Logic Programming:
- Simple to write code
- Easy to debug
- Fast development
- Ideal for expressing complex ideas and algorithms
- Allows data to be represented both extensionally and intentionally
Languages that support Logic Programming:
- Prolog
- Absys
- ALF (algebraic logic functional programming language)
- Alice
- Ciao
Functional Programming
Functional Programming is by far the most used declarative programming paradigm, the basic premise is that programs are constructed by applying and composing functions.
Let's take a look at this example:
// Generating a Fibonnaci sequence
fib(n)
if (n <= 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
Functional Programming has to follow a set of principles:
- Pure Functions — All functions must be pure, meaning that it should have no side effects and it should be deterministic, returns the same result if given the same arguments
- Immutability — When data is immutable, its state cannot change after it’s created. If you want to change an immutable object, you can’t. Instead, you create a new object with a new value.
- Referential Transparency — Basically because we got pure functions and immutability, we can replace all function calls with their underlying return values and the program would still work.
- Functions as First-Class Entities — It means that functions can be passed to other functions as arguments.
- Higher-Order Functions — Higher-Order functions are basically functions that accept other functions as arguments and returns a function as its result.
Benefits of Functional Programming:
- It helps us to solve problems effectively in a simpler way.
- Improves modularity.
- Allows us to implement lambda calculus to solve complex problems.
- Reduces complex problems into simple pieces.
- Debugging is easy.
Languages that support Functional Programming:
- Haskell
- JavaScript
- Scala
- Erlang
- Lisp
- Clojure
Reactive Programming
Reactive programming is a programming paradigm that is concerned with data streams, and how it reacts to it.
For example, you can have multiple streams (also called observers)
- Click events
- HTTP requests
- Ingested messages
- Availability notifications
For each stream, you have a subscriber that responds accordingly.
Benefits of Reactive Programming:
- Simple to do asynchronous work
- Avoid “callback hell”
- Simple to compose streams of data
- Simplifies complex threading
- Cleaner and maintainable code
- Easy to implement back-pressure
Languages that support Reactive Programming:
- Scala
- Haskell
- Elm
- Javascript
Conclusion
There aren't all the programming paradigms available, there are many others I couldn't cover in this article. But I covered the main ones at the least, and I hope you have a better understanding of each.
Thanks for reading, and don't forget to follow me on Twitter.
Top comments (9)
People absolutely mention throwing exceptions as doing the same thing. The distinction is that exceptions being used for control flow is usually (almost always) an anti-pattern, for the same reason that using gotos for that purpose is.
But exceptions have an idiomatic use that is easier (if not trivial) to manually parse. They're virtually worthless without try/catch, but in conjunction with that, it's about as clean a way to handle "exceptional" cases as we have, short of algebraic effects. So it's likely that people feel that exceptions aren't as often misused as gotos are, because they have that idiomatic use case.
There may be truth to that in certain workflows. But understand that I was not saying that people like exceptions as a control flow tool more than goto, that they serve a much different purpose. They are, essentially, tools that provide nominative ways of dealing with leaky abstractions in a situation where indeterminism and side effects are virtually inevitable without rewriting the abstractions. If the program/logic in question doesn't lend itself to making it obvious what exceptions can occur, they're terrible. But at the very least, exceptions do make it possible to write code that can still model the usual control flow, while also ergonomically handling those side effects. Gotos don't have that kind of syntactic benefit. They're a blunt force tool, and are too easy to use AS your control flow. They are trivially easy to write, but don't lend themselves to readability.
I'm not arguing in favor of using exceptions. I'm merely trying to give context for why people don't dislike them as much as gotos. I don't personally love exceptions either, but if I had to pick between one or the other, I'll take the ones that are at least predictable in their behavior. Exceptions usually propagate, and thus return control to the caller, and try/catch in a language like C# is so easy that it's usually not going to affect readability that heavily. Gotos let you do all kinds of ridiculously convoluted things that I'd rather we just not be able to do.
They're both terrible if used poorly, but gotos are more often misused in the languages that have them. (At least, when they aren't restricted to forward gotos, but even then.)
Good overview, thanks!
Well, you started the discussion. :-)
Frankly I see no reason to have anyone point 'proven', that's on you. Both ways have their pros and cons.
It's not like rust cannot have unhandled panics, which crashes the app as well, resulting in a stack unwind or a simple abord, depending on setup.
And apparently you can still catch a panic with catch_unwind, except it might not catch all panics, so yeah, so much for predicability.
It's not entirely unpredictable at all. They will unwind the stack and only that, you can stop them from doing so and handle it by having a try/catch in it's path or let the program handle it (with a crash).
While not perfect they are still a decent enough way of handling errors. Yes other languages might not have them but that does not mean exception are without merit.
Frankly both go and rust reminds me of the old days in C when you just returned any error from any funciotn and had to check it with an if statement after. Sure with some updates like returning multiple results, tuples whatnot. Some might prefer that, others not. But frankly I hardly see these tings as a deal breaker of a language, more like one of it's quirks. You just learn to live with it when coding in that language.
Well functional programming is a type of Imperative paradigm not a Declarative paradigm.. Or am I wrong?
What makes you think so?
exception always goes up the stack, until it hit catch or the global catch. YOu cannot jumpto any point in your source
thanks, very clear explanation