A functional program is a program that describes how to compute a function, in the mathematical sense of the word, that is, a contraption that always spits out the same output for the same input and does nothing else except for spitting out output.
Functions in the mathematical sense are different from “functions” in most programming languages, which to avoid confusion I shall henceforth call “procedures,” in that (a), not every function can be described by a procedure, since some are incomputable, and (b) not every procedure describes a function, since some have side effects and/or return different outputs on different occasions for the same input.
In other words, Functional programming (also called FP) is a way of thinking about software construction by creating pure functions. It avoids concepts of shared state, mutable data observed in Object-Oriented Programming. Functional languages emphasize on expressions and declarations rather than the execution of statements. Therefore, unlike other procedures which depend on a local or global state, value output in FP depends only on the arguments passed to the function.
What should every programmer know about functional programming?
Well, everything because functional programming is the future. Let me explain you this. Gordon Moore, the co-founder of Intel, gave a law in 1965 according to which the no of transistors on an Integrated Circuit doubles every two years and simultaneously the CPU speed also doubles. But for the past decade, this law has started to fail. But only the second part i.e. CPU speed is not doubling.
It means that we can still achieve more speed only if we put multiple cores in the CPU. So to take the full advantage of the computing power, programmers need to write multi-threaded code that can run on multi-core processors. But one more problem arises with multi-threaded code is that when more than one core tries to access a data object, data isolation becomes a headache.
To tackle this problem Functional Programming is used that does not manage the state of a data object and hence can be used to execute program parallelly on the multi-processors system.
Immutability: The literal meaning of immutability is unable to change. In the Functional Programming world, we create values or objects by initialising them. Then we use them, but we do not change their values or their state. If we need, we create a new one, but we do not modify the existing object’s state. Let me give you some examples in Scala.
Scala has two types of variables.
The var stands for a variable, and the val stands for value. You can initialise a var, and later you can reassign a new value to the var.
The next one is the val. The val is a constant. That means, once initialised, You cannot change it.
So, reassignment to val is an error. You cannot do it. Since Scala is a hybrid language, it supports var and val both. However, when you are using Scala as a Function Programming language, It recommends using val instead of var. Using val will force you to create programs using only the constants. You might be wondering with below question.
Function Composition: Function composition is taking the return value of one function and passing it as an argument to another function. It’s common enough that functional programmers have turned it into its own operation. In this episode, we go deep into why it’s important and how you can use it and write it yourself.
This functionality of calling the first function and passing its result directly to a second function can be extracted to avoid code repetition. Let’s create a
compose function that represents this concept:
In the most general terms, we can break the whole discipline of programming down into a couple of stages:
- The understanding of complex business logic of a problem
- Breaking the problem down to a set of smaller problems
- Solving the smaller problems one at a time
- Putting it back together to form a coherent solution
Number 4 on this list, composing programs from smaller logical units, is one of the most important and challenging aspects of software engineering. Function composition is the main tool that helps us achieve that in the functional style of programming.
Referential Transparency: Functional programs should perform operations just like as if it is for the first time. So, you will know what may or may not have happened during the program’s execution, and its side effects. In FP term it is called Referential transparency. In programming, referential transparency applies to programs. As programs are composed of subprograms, which are programs themselves, it applies to those subprograms, too. Subprograms may be represented, among other things, by methods.
That means method can be referentially transparent, which is the case if a call to this method may be replaced by its return value:
In this example, the
mult method is referentially transparent because any call to it may be replaced with the corresponding return value.
The definition of a pure function is:
- The function always returns the same result if the same arguments are passed in. It does not depend on any state, or data, change during a program’s execution. It must only depend on its input arguments.
- The function does not produce any observable side effects such as network requests, input and output devices, or data mutation.
That’s all there is to a pure function. If it passes the above two requirements it’s pure. You’ve probably created a pure function in the past without even realising. Before I show you an example of a pure and unpure function lets discuss the dreaded “side effects”. We have all written pure functions in the past knowingly or unknowingly. Take a look at a pure function below, that returns a sum of two numbers given two input arguments.
The output of this function will always remain the same, if the same input arguments are passed in. Simple enough. Pure functions are simple building blocks that have a predictable behavior always. Hence they always return the same predictable output, without any external influence. Here is a simple example, on what an impure function looks like.
Here the function is considered impure because it is not using the argument that was passed to it. Instead, it uses an external value which is subject to change. Therefore, making the function impure due to external interference/side-effects. Alright, let’s talk about requirement two on side-effects.
Higher-Order Functions: Higher-order functions either take other functions as arguments or return them as results. Higher-order functions allow partial applications or currying. This technique applies a function to its arguments one at a time, as each application returning a new function which accepts the next argument. In simple words, a Higher-Order function is a function that receives a function as an argument or returns the function as output.
Array.prototype.reduce are some of the Higher-Order functions built into the language.
Let’s say we have an array which contains objects with name and age properties. We want to create an array that contains only the persons with full age (age greater than or equal to 18).
Without Higher-Order Function
With Higher-Order Function filter
Arity: The arity of a function is the number of arguments that the function takes. A unary function is a function that only takes a single argument:
Functions with two or more arguments are also important because some of the most common FP patterns and techniques (for example, partial application and currying) have been designed to transform functions that allow multiple arguments into unary functions. There are also functions with three (ternary functions) or more arguments. However, functions that accept a variable number of arguments, known as variadic functions.
Curried Functions: A curried function is a function that takes multiple arguments one at a time. Given a function with three parameters, the curried version will take one argument and return a function that takes the next argument, which returns a function that takes the third argument. The last function returns the result of applying the function to all of its arguments.
You can do the same thing with more or fewer parameters. For example, given two numbers,
b in curried form, return the sum of
This is a function that takes two arguments, a and b, and returns their sum. We will now curry this function:
This is a function that takes one argument, a, and returns a function that takes another argument, b, and that function returns their sum.
The first statement returns 7, like the add(3, 4) statement. The second statement defines a new function called add3 that will add 3 to its argument. This is what some people may call a closure. The third statement uses the add3 operation to add 3 to 4, again producing 7 as a result.
Functors: Remember when we talked about
map? We only used map on arrays, but you can actually implement
map on other data structures, e.g. trees, streams, promises…
In the code above, I used
=== to keep it simple, but I should have checked for deep equality. If you don’t know what this means, don’t worry, you should be able to understand the meaning anyway. Nonetheless, if you want to learn more about it, this StackOverflow thread is a good place to start.
The advantage of using a functor is that the container is now abstracted away. E.g., in streams, we don’t need to care about asynchronous data handling, we can just use a stream like an array. Another cool feature of functors is that you can chain map calls because the map the function returns another functor.
- Monads: Giving a definition of a monad is somewhat tedious and requires a bit of theory, so we are first going to build an intuition for them through examples.
Array Monad: Let’s write a function that duplicates every item in an array (e.g.
Let’s first try with a map:
Running the code, you can see that the result is not quite what we wanted:
[[1,1],[2,2],[3,3]]. Wouldn’t it be great if we had a function that automatically “flattened” the array we returned into
Although the array’s implementation of
flatMap is very interesting and useful, it’s not the only one: we can, for example, use flatMap to maintain knowledge of previous states of a system. As always, let’s start with an example.
To use a closure, define a function inside another function and expose it. To expose a function, return it or pass it to another function. The inner function will have access to the variables in the outer function scope, even after the outer function has returned.
In the example above, the `.get()` the method is defined inside the scope of `getSecret()`, which gives it access to any variables from `getSecret()` and makes it a privileged method. In this case, the parameter, `secret`. Objects are not the only way to produce data privacy. Closures can also be used to create stateful functions whose return values may be influenced by their internal state.
Conclusion: Functional Programming combines the flexibility and power of abstract mathematics with the intuitive clarity of abstract mathematics.
To learn more about programming, click here.