DEV Community

loading...
Cover image for The Simple Guide to Programming Paradigms

The Simple Guide to Programming Paradigms

Tamerlan Gudabayev
Learning is fun
Originally published at softwareadventuring.com ・6 min read

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

Alt Text

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

Alt Text

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

Alt Text

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:

  • Conditionalsif/then/else
  • Repetitionwhile/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

Alt Text

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]
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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]
}
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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.

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

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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).
Enter fullscreen mode Exit fullscreen mode

Let's break this down:

  1. The first line expresses that Socrates is a man.
  2. The second line says "X is mortal if X is a man".
  3. 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

Alt Text

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);
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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.

Discussion (15)

Collapse
hanpari profile image
Pavel Morava

Every time I see that go-to was bad because it encouraged jumping to different places in a codebase, I wonder why nobody mentions throwing exceptions doing the same thing.

Collapse
vrobweis profile image
Vincent Weis

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.

Collapse
hanpari profile image
Pavel Morava

The problem with exceptions is they are side effects, and exceptionally harmful and unpredictable. It is not about the control flow; or if yes, then it is about bypassing the predictable control flow.

Thread Thread
vrobweis profile image
Vincent Weis

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.)

Collapse
fredrikbonde profile image
Fredrik Bonde

exception always goes up the stack, until it hit catch or the global catch. YOu cannot jumpto any point in your source

Collapse
hanpari profile image
Pavel Morava

Well, this is actually even worse. With go-to one at least know where the code goes.

Exceptions are absolutely unpredictable, especially when left uncaught.

This no coincidence that modern programming languages like GoLang or Rust did not introduce them.

Thread Thread
fredrikbonde profile image
Fredrik Bonde

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.

Thread Thread
hanpari profile image
Pavel Morava

Seriously, I don't want to discuss the errorprone nature of exception throwing.

If you think they are predictable and safe way of handling errors, so be it.

Modern approaches in programming language design seemingly prove my point, but who am I to judge?

So far as I am concerned the exception throwing was a blind avenue in programming and will eventually disappear.

I saw too many unhandled exceptions in my life to call them a predictable or safe concept.

Thread Thread
fredrikbonde profile image
Fredrik Bonde

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.

Thread Thread
hanpari profile image
Pavel Morava

I am far from saying that Rust or GoLang are ideal. Especially, how Go treats errors is rather unfortunate, yet still better than exception throwing. That's all to this.

Collapse
vladislavmurashchenko profile image
VladislavMurashchenko

Good overview, thanks!

Collapse
muhammadmuzammilqadri profile image
Muhammad Muzammil

Well functional programming is a type of Imperative paradigm not a Declarative paradigm.. Or am I wrong?

Collapse
miguelmj profile image
MiguelMJ

What makes you think so?

Collapse
josethz00 profile image
Tomé

thanks, very clear explanation

Collapse
showwaiyan profile image
DamianZort

The best explanation