Programming is an act of communicating with a machine, making it executing commands in a certain order. With increasing counts of task and complexity, we also invent more concepts to help ourselves cope with the bigger codebase. From programming with pure machine code, which only a handful of people could have done we develop a set of symbols called Assembly which can work directly with an associating CPU architecture. This worked for a while but then a programmer named Dennis Ritchie started the revolution with his invention of C programming language which gave programmers of that time more safety without losing too much control over the hardware; C could be seen as the first high-level programming language ever with imperative procedural features. Basically we talk about a programming language that allows usages of statements to change the program’s state.
From this point, the imperative procedural paradigm continues to have a big impact on how programmers think and design systems. In the early and mid-1990s, a new player started to gain domination. We talk about the object-oriented paradigm, which in its root is not a new concept at that time. But with the rise of Java, everybody started to use OOP as a solution for every possible problem, if you don’t know Java or a similar language, you are hopelessly lost. The original idea of OOP specifies the explicit need for modeling the real world’s problem in class and object. Everything could be seen as an object, which contains state and methods. For increasing numbers of classes, the system designer can choose one of many off-shelves solutions, the so-called sign patterns which basically tell you how to simulate the relationships of classes in a reusable way. You basically can just shut down your brain, open the book written by Gang of Four and choose randomly a design pattern that seems to fit your problem mostly.
OOP seems to be the natural way of many students since they don’t know any other paradigm and Java is the most popular programming language in almost every university out there. However, if you put increasingly code into your codebase, spaghetti code will be unavoidable. Recently I have to work with an open-source project, which btw. was written and documented in the most OOP-way you can possibly imagine. In order to understand what a class does, you would have to dive deep into at least 7 – 8 interfaces, which mostly would do nothing except adding a small method into the chain. The complexity as a nightmare to deal with and I don’t think this is how software engineering should work. There are very intelligent people who should spend more time on the algorithm and data structure aspect of the problem since that is where they ace. The business changes too fast for a programmer to model everything in static classes and objects, which are also incredibly hard to be modified. Everywhere you go, everything seems to be an object – except that is not always the case. That is why I think it is important to take a look at other perspectives computer science has to offer.
During some research, I found many new concepts of functional programming to be very useful to reduce a project’s complexity and save developers’ nerves. Functional programming itself is something that existed for much longer than object-oriented programming, dating back to the ancient days of Turing machines. In order to understand functional programming, it is important to hear of lambda calculus, which is a mathematical body introduced by Alonzo Church in the 1930s and is a way of expressing computation through the use of functions we call Lambdas. A lambda is composed of three elements: variables, functions, and applications. The most basic lambda is the identity function . The first is the function’s argument, and the second is the body of the function. This is pretty the same concept you have learned about in school, the evaluation of expression will only the needs parameters and outputs of lambda and the calculation can be done. All lambdas, therefore, can be written anonymously without a name – because the only portion of a function header that affects its execution is the list of arguments and output.
When invoked, all lambdas will go through a process called currying. This means when a function with multiple arguments is called, it will execute the function once but it only set one variable in the parameter list. In the end, a new function is returned with 1 less argument – the one that was just applied – and this new function is immediately invoked. This happens recursively until the function has been fully applied and the result can be returned. This concept may be also taught in every calculus lecture under the name mathematical induction. And because we are talking about functional programming, this concept of recursion works. Otherwise, if a state can be changed, currying could produce unsafe results.
One of the major benefits of functional programming is the ability to write programs that run concurrently and that do it properly without side effects – meaning that common concerns such as deadlock, starvation, and thread-safety aren’t an issue. If you have implemented a concurrent program in Java and you would know that concurrency is a nightmare because the state of an object could be changed at any given moment. Those benefits can only be achieved if we make sure the following concepts are implemented in our program.
First of all, we need as many pure functions as possible. All functions can be pure in the sense that they are idempotent, meaning calling a function multiple times with the same arguments will always return the same value. Moreover, no side effects should be allowed to happen during the function’s execution. A side effect is basically a state change in something other than the function that is currently executing. Modifying a variable defined outside of a function is a no-go. To enforce this concept, you can make every attribute of your classes final, which means they can not be changed after initialization. Each change on an object’s attributes will not change its state but rather yields another object with the changed attribute. At first, this might seem to be a big constraint for function programming – but think about it. If you know for sure that a function won’t modify any sort of state outside the function itself, then you have full confidence that you can call this function in any scenario.
Relating to the first concept, every variable of your program should be immutable. This one is pretty simple, you must not modify a variable after it has been initialized. You just can’t. You can create another variable with a changed value, but the old variable is still the same. Once you create a variable and set its value, you can have full confidence knowing that the value of that variable will never change. Java, for example, enforces every String variable to be immutable, that why you have to reassign a variable if you want to change its value.
If you know some basic calculus, you would know that functions can be composited into a new function, something like for and are different functions respectively. Function composition is a very important thing in FP, as the usual composition of functions in calculus, the result of each inner function will be passed as the argument of the next function, and the result of the last one is the result of the whole. This concept, for example, is used extensively in C. Image you want to write a generic function that sort elements of an array. Since we have no possibility to tell how to compare two elements of an array, we need a function as a parameter which will be implemented to tell us how to compare two elements. The comparing function could be understood as an inner function while only the result of the outer function will be returned.
Typical functions that we would want to composite are for example filter, map, flat map, reduce, stream, apply, collect.
Functions in FP are first-class and can be high-order, that means first-class functions are treated as the first-class variable. The first-class variables can be passed to functions as a parameter, can be returned from functions or stored in data structures. High order functions are functions that take other functions as arguments and they can also return functions.
Functional programming involves a significantly different state of mind and a huge change in how we program. But the concepts of this paradigm are so powerful that I think every advanced programmer should take some time to dive deeper into the topic and add at least some new tools to his toolkit.