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