Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Programming Paradigms

Programming Languages

Programming languages are used to describe the behavior of a computer (includes mobile phones, tablets, embedded devices and servers) to perform a specific task. There are many programming languages and each of them has its own strengths and weaknesses. Some of the programming languages are general purpose and some of them are domain specific. In this chapter, we will look different programming languages and their paradigms. We will try to classify them with different criteria such as typing, execution model, and paradigm.

A programming language is a formal specification of programs that can be executed by a computer. For this reason, it is important to know the difference between a programming language and implementation of a programming language.

Programming LanguageImplementation(s)
CGCC, Clang
JavaOpenJDK, Oracle
PythonCPython, PyPy
RubyMRI, JRuby
JavascriptV8, SpiderMonkey
PHPZend Engine
Gogc, gccgo
Rustrustc
SwiftSwift

The syntax of a programming language describes the valid "sentences" that can be written in the language. On the other hand, the semantics of a programming language describes the meaning of the "sentences" that can be written in the language.

Programming languages often categorized by their paradigms, execution model and typing.

Compiled and Interpreted Languages

Most programming languages can be categorized as compiled or interpreted.

Programming LanguageInterpretedCompiled
JavascriptYesNo
TypescriptYesYes
PythonYesNo
JavaNoYes
C#NoYes
PHPYesNo
GoNoYes

Imperative Programming Languages

Declarative Programming Languages

Object Oriented Programming

Object orient programming is a programming paradigm that organizes code into objects that combines data and behavior in a single unit. Commonly a unit of data stored in an object is called attribute and the a unit of behavior is called method. Object oriented programming is one of the most popular programming paradigms in the world. Most modern programming languages support object oriented programming today.

Why we need to combine data and behavior in a single unit? Lets examine a simple counter example. Suppose that we want to write a program that counts the number of times a button is clicked. We want to print the number to the console when a user clicks a button. We can write a simple program like this:

// Create a simple variable to store the count
var count = 0;

// Create a function to increment the count
function inc() {
  // Increment the counter
  count = count + 1;

  // Print the counter to the console
  console.log(count);
}

// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc function when the button is clicked
var button = document.createElement('button');
button.textContent = 'Click me';
button.onclick = () => inc();
document.body.appendChild(button);

This program will work as expected. However there are some problems with this program. The first one is that the count variable is a global variable. This means that any part of the program can change the value of the count variable. This can lead to bugs that are hard to find. The second one is related to creating multiple counters. If we want to create multiple counters, we need to create multiple count variables and inc functions. This can lead to code duplication and hard to maintain code.

// Create a simple variable to store the count1
var count1 = 0;

// Create a function to increment the count1
function inc1() {
  // Increment the counter
  count1 = count1 + 1;

  // Print the counter to the console
  console.log(count1);
}

// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc1 function when the button is clicked
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => inc1();
document.body.appendChild(button1);

// Create a simple variable to store the count2
var count2 = 0;

// Create a function to increment the count2
function inc2() {
  // Increment the counter
  count2 = count2 + 1;

  // Print the counter to the console
  console.log(count2);
}

// Create a button element
// If you do not know javascript, just know that this block of triggers
// the inc2 function when the button is clicked
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => inc2();
document.body.appendChild(button2);

This code is too dirty and hard to maintain. Object oriented programming solves these problems by introducing a method to combine data and behavior in a single unit. This unit is called an object. Lets rewrite the counter example using objects.

// Create counter1 object
var counter1 = {
  count: 0, // <-- Attribute
  inc: function () {
    counter1.count = counter1.count + 1;
    console.log(counter1.count);
  }, // <-- Method
};

// Create counter2 object
var counter2 = {
  count: 0, // <-- Attribute
  inc: function () {
    counter1.count = counter1.count + 1;
    console.log(counter1.count);
  }, // <-- Method
};

// Create a button element
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc();
document.body.appendChild(button1);

// Create a button element
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc();
document.body.appendChild(button2);

This code is much cleaner compared to previos one. However as you can notice, the inc method contains the same code except the object name. In order to remove code duplication, we can receive the object the we interested in as a parameter to the inc method.

// Create counter1 object
var counter1 = {
  count: 0, // <-- Attribute
};

// Create counter2 object
var counter2 = {
  count: 0, // <-- Attribute
};

function inc(counter) {
  // <-- Receive the object as a parameter (again it is a method)
  counter.count = counter.count + 1;
  console.log(counter.count);
}

// Create a button element
var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => inc(counter1); // <-- Pass counter1 object to the inc function
document.body.appendChild(button1);

// Create a button element
var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => inc(counter2); // <-- Pass counter2 object to the inc function
document.body.appendChild(button2);

By passing the object to the inc method, we removed the code duplication. This is the basic idea of object oriented programming. Instead of passing the object that we interested in as an explicit parameter, we can use a special keyword called this which is passed to the function implicitly. Lets rewrite the counter example using this keyword.

var counter1 = {
  count: 0,
  inc: function () {
    this.count = this.count + 1;
    console.log(this.count);
  },
};

var counter2 = {
  count: 0,
  inc: function () {
    this.count = this.count + 1;
    console.log(this.count);
  },
};

var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc(); // <-- No need to pass the object explicitly
document.body.appendChild(button1);

var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc(); // <-- No need to pass the object explicitly
document.body.appendChild(button2);

However instead of specifying each attribute's initial value one by one we can use a function that will create the object for us. This function is called a constructor. Lets rewrite the counter example using a constructor.

function Counter() {
  // <-- Constructor function
  return {
    count: 0,
    inc: function () {
      this.count = this.count + 1;
      console.log(this.count);
    },
  };
}

var counter1 = Counter(); // <-- Create counter1 object
var counter2 = Counter(); // <-- Create counter2 object

var button1 = document.createElement('button');
button1.textContent = 'Click me';
button1.onclick = () => counter1.inc();
document.body.appendChild(button1);

var button2 = document.createElement('button');
button2.textContent = 'Click me';
button2.onclick = () => counter2.inc();
document.body.appendChild(button2);

This example shows the basic idea of object oriented programming and covers attribute, method, and constructor concepts. Although there are some improvements that can be made, this example is enough to understand the basic idea of object oriented programming and we will cover these improvements in the following sections.

Classes

A class is a blueprint for an object. It defines common attributes and methods that objects created from the class will have. In the previous examples, we created objects using object literals and constructor functions. However modern programming languages provide a special syntax to define classes. Lets rewrite the counter example using classes.

class Counter {
  constructor() {
    this.count = 0;
  }

  inc() {
    this.count = this.count + 1;
    console.log(this.count);
  }
}

var counter1 = new Counter();
var counter2 = new Counter();

A class is a type in a typed programming language. This means that we can create a variable that has a type of a class. In the previous examples, we created objects using object literals and constructor functions. Lets examine an example for Java programming language.

class Counter {
  int count = 0;

  void inc() {
    count = count + 1;
    System.out.println(count);
  }
}

public class Main {
  public static void main(String[] args) {
    Counter counter1 = new Counter(); // <-- Type of counter1 is Counter
    Counter counter2 = new Counter(); // <-- Type of counter2 is Counter
  }
}

Therefore, you should notice that a class is not a concrete object. It is just a blueprint for an object. When you create an instance of a class, it becomes an object. In the previous examples, Counter is a class and counter1 and counter2 are objects.

Encapsulation

Encapsulation is a concept that restricts direct access to some of the object's attrubutes and methods. Encapsulation is used to protect the object's state from being modified by other objects. In object oriented programming, we can restrict access to an object's attributes and methods by using access modifiers.

For instance, in our counter example, the count attribute is public which means that any part of the program can change the value of the count attribute. This can lead to bugs that are hard to find. We can restrict access to the count attribute by using access modifiers. Access modifiers are language specific and they can be different from language to language. Lets examine an example for Typescript programming language.

class Counter {
  private count = 0; // <-- Private access modifier

  private printToConsole() {
    // <-- Private access modifier
    console.log(this.count);
  }

  inc() {
    // <-- Public access modifier by default
    this.count = this.count + 1; // <-- Access to the private attribute
    this.printToConsole(); // <-- Access to the private method
  }
}

var counter1 = new Counter();
var counter2 = new Counter();

counter1.inc(); // <-- Access to the public method
counter2.inc(); // <-- Access to the public method

In this example, the count attribute is private which means that only the methods of the Counter class can access the count attribute directly. The printToConsole method is also private which means that only the methods of the Counter class can access the printToConsole method directly. The inc method is public by default which means that any part of the program can access the inc method.

Inheritance

Inheritance is a concept that allows a class to inherit attributes and methods from another class. The class that inherits attributes and methods is called a subclass and the class that provides attributes and methods is called a superclass. Lets examine an example for Typescript programming language.

class Counter {
  private count = 0;

  private printToConsole() {
    console.log(this.count);
  }

  inc() {
    this.count = this.count + 1;
    this.printToConsole();
  }
}

class ResettableCounter extends Counter {
  reset() {
    this.count = 0;
    this.printToConsole();
  }
}

var counter1 = new ResettableCounter();
var counter2 = new Counter();

counter1.inc(); // <-- Access to the public method of the Counter class
counter1.reset(); // <-- Access to the public method of the ResettableCounter class
counter2.inc(); // <-- Access to the public method of the Counter class

In this example, the ResettableCounter class is a subclass of the Counter class. The ResettableCounter class inherits the count attribute and the printToConsole method from the Counter class. The ResettableCounter class also has a reset method that is not available in the Counter class. The ResettableCounter class can access the count attribute and the printToConsole method directly. The ResettableCounter class can also access the inc method of the Counter class.

Also, it is possible to override the methods of the superclass in the subclass. Lets examine an example for Typescript programming language.

class Counter {
  private count = 0;

  private printToConsole() {
    console.log(this.count);
  }

  inc() {
    this.count = this.count + 1;
    this.printToConsole();
  }
}

class ResettableCounter extends Counter {
  private printToConsole() {
    console.log('ResettableCounter printToConsole method');
    console.log(this.count);
  }

  reset() {
    this.count = 0;
    this.printToConsole();
  }
}

var counter1 = new ResettableCounter();
var counter2 = new Counter();

counter1.inc();
counter1.reset();
counter1.inc();
counter2.inc();

Therefore, by using inheritance, we can remove code duplication and create a hierarchy of classes.

Polymorphism

Work in Progress

Abstraction

Abstraction is a concept that hides the implementation details of an object and only shows the necessary details to the outside world. Abstraction is used to simplify the complexity of an object. In object oriented programming, we can use abstract classes and interfaces to achieve abstraction.

An interface is a specification of a class. It defines a set of methods that a class must implement. Lets examine an example for Typescript programming language.

interface Counter {
  count: number;
  inc(): void;
}

class InMemoryCounter implements Counter {
  count = 0;

  inc() {
    this.count = this.count + 1;
    console.log(this.count);
  }
}

class LocalStorageCounter implements Counter {
  count = 0;

  constructor() {
    this.count = parseInt(localStorage.getItem('count') || '0');
  }

  inc() {
    this.count = this.count + 1;
    localStorage.setItem('count', this.count.toString());
    console.log(this.count);
  }
}

// Gets an instance of the Counter interface and increments the counter n times
function countNTimes(counter: Counter, n: number) {
  for (var i = 0; i < n; i++) {
    counter.inc(); // <-- We do not know which concrete class will be used here
  }
}

var counter1 = new InMemoryCounter();
var counter2 = new LocalStorageCounter();

counter1.inc();
counter2.inc();

// Refresh the page and run the program again

When you refresh the page and run the program again, you will see that the count attribute of the LocalStorageCounter class is persisted in the local storage. This is an example of abstraction. The countNTimes function does not know which concrete class will be used. It only knows that the class will have a count attribute and an inc method. It does not care about the implementation details of the class.

This way of calling a method without knowing the implementation details is called dynamic dispatch. Dynamic dispatch is a mechanism that determines which method to call at runtime. In the previous example, the countNTimes function uses dynamic dispatch to call the inc method of the Counter interface. On the other hand, counter1.inc() and counter2.inc() calls the inc method of the InMemoryCounter and LocalStorageCounter classes respectively. This is called static dispatch. Static dispatch is a mechanism that determines which method to call at compile time.

Prototype Chains (JavaScript Specific, Optional)

Generally, an object oriented programming language has two choices for implementing object oriented programming. The first one is class-based and the second one is prototype-based. In class-based object oriented programming, a class is a blueprint for an object and an object is an instance of a class.

Work in Progress

Functional Programming

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. It is a declarative programming paradigm, which means that programming is done with expressions or declarations instead of statements.

Some programming languages support functional programming paradigm in addition to other paradigms. For instance, JavaScript supports functional programming paradigm in addition to object-oriented programming paradigm. On the other hand, some programming languages are designed to support only functional programming paradigm. For instance, Haskell is a purely functional programming language.

We will look at some of the core concepts of functional programming in this chapter.

Pure Functions

There is a difference between functions in mathematics and functions in programming. In mathematics, a function is a relation between a set of inputs and a set of possible outputs. Therefore, a function satisfies these two properties:

  1. A function should return the same output for the same input.
  2. A function should not have side effects.

However, in programming, a function can have side effects and it can return different outputs for the same input. For instance, a random number generator function is a function that returns different outputs for the same input. It does not take any arguments but it returns a different output each time you call it. (If it would be a function in a mathematical sense, it should return only one value because it does not take any arguments.).

A function that prints something to the screen is a function that has side effects. It does not return anything but it changes the state of the screen. Or a function that send a request to a server is a function that has side effects because it manipulates the "outside world" by using electrical signals. A function that reads a file from the disk is also a function that has side effects because it causes the disk to spin and read the file that is located on a specific location on the disk.

Lets look at some examples;

// Some pure functions
(x, y) => x + y;
(person) => (person.age > 18 ? 'Adult' : 'Child');
(str) => JSON.parse(str);

// Some impure functions
(x, y) => x + y + Math.random();
(x, y) => console.log(x + y);
() => fs.readFileSync('file.txt');
(x, y) => fs.writeFileSync('file.txt', x + y);
(x, y) => {
  console.log(x + y);
  return x + y;
};

In some functional programming languages, all functions must be pure functions (such as Haskell). Pure functions are very useful because they are easy to test. You can test a pure function by giving it an input and checking the output. If the output is the same as you expected, then the function is working correctly. Also, pure functions are easy to understand and most importantly, they are easy to compose. Therefore, building complex systems with pure functions is much easier than building them with impure functions. Because, they are guaranteed to work correctly for each individual function and composing them will not result any unexpected behavior.

First Class Functions

Immutablity

One of the most bug-prone parts of a code is mutable variables (or variables). Because, you can change the value of a variable at any time from any place. This can cause a lot of bugs. In order to avoid these bugs, you should use immutable variables as much as possible. Instead of using let and var, you should use const in order to make your variables immutable. Also, you should avoid mutating objects and arrays. You should use map, filter, reduce functions to manipulate arrays.

Also you should be careful about scoping. Because scopes can restrict the usage of a variable. If you are defining a variable in a global scope, you can use it from anywhere. This can cause a lot of bugs. For this reason, please try to use local variables as much as possible and keep your scopes as small as possible.

It may be seem to be impossible to avoid mutable variables. However, you can use some techniques to avoid them. For instance, you can use Map, Filter and Reduce functions can be used. They are higher order functions which means that they take a function as an argument and they return a new array. These functions are very useful and they are reduces the need for loops and mutable variables. You should use these functions as much as possible. Also there are some other functions in javascript that you can use to manipulate arrays. For instance, forEach, some, every, find, findIndex etc. You should use these functions as much as possible. Also, you can use lodash library to manipulate objects and arrays.

Also, you should return results of a function with return statement. Mutating a global variable and then reading it from another function is a bad practice. Instead of that, you should return the result of a function with return statement. This will make your code much more clear and easy to understand.

If you want to learn more about immutability, you can check Functional Programming Paradigm.

Common Higher Order Functions

Side Effects with Pure Functions

You may wonder that how is it possible to write a program only with pure functions. Because, in real world, you need to interact with the outside world. You need to read files, send requests to servers, print something to the screen etc. However, this is possible in Haskell. At the end of the day nothing is pure because the nature of the problem that you want to solve is not pure. Lets look at an example from Haskell.

-- You can run this program with `runhaskell` command in your terminal but you need to have ghc installed on your system.

-- putStrLn is a function that takes a string and returns an IO action that DESCRIBES how to print the given string to
-- the screen. Type signiture of putStrLn is:
-- putStrLn :: String -> IO ()

-- main is a DESCRIPTION of the program. It is an IO action that DESCRIBES how to run the program.
-- It is not a function it is just a value that describes how to run the program.
main:: IO ()
main = putStrLn "Hello, World!" -- In haskell, we call functions with space instead of parentheses.

You should think IO as a recipe that describes what a computer should do. Although, executing instructions in a computer is not a pure operation, the recipe that includes these instructions itself is a completely pure value. Lets look at another example.

-- This is a pure function that takes a string and returns a string
greet :: String -> String
greet name = "Hello, " ++ name ++ "!"

main:: IO ()
main = putStrLn (greet "John")

Lets examine the code above. The haskell compiler first looks at the main function. It sees that main is an IO action that describes how to run the program. However in order to get a complete list of instructions, it needs to evaluate the putStrLn (greet "John") expression. In order to evaluate this expression, it needs to evaluate the greet "John" expression. Therefore it evaluates the greet function with the argument "John". The greet function returns a string that is "Hello, John!". Then the compiler evaluates the putStrLn function with the argument "Hello, John!". The putStrLn function returns an IO action that describes how to print the given string to the screen. We can see its like this:

main = `Print "Hello, John!" to the screen`

After that, the compiler runs the instructions that are described in the main function. It prints the string "Hello, John!" to the screen. Lets look at a more complex example.

greet :: String -> String
greet name = "Hello, " ++ name ++ "!"

-- Lets write our main function that will ask the name from the user and greet the user
main :: IO ()
main = putStrLn "What is your name?"

Summary

Exercises

Extra Resources