Discover functional programming: a paradigm where functions are king! Dive into its principles, from immutability, pure functions to higher-order functions, and see how it facilitates efficient, bug-free, and maintainable code.
There are so many programming languages out there that we need some sort of classification for them. Of course, the technology community has many classifications for them, but one of the most used involves programming paradigms.
A programming paradigm is basically a way of classifying languages depending on their characteristics. Thus, some paradigms focus on the implications of their execution models, while others deal more with code organizations or syntax and grammar styles.
There are several programming paradigms, but probably the best known is object-oriented programming. OOP is so popular that people who have a very tangential relationship with the world of programming have certainly heard of it.
The reason for this popularity? OOP has modularization for easy problem solving, allows code reuse through inheritance, has flexibility through polymorphism, and is great for problem solving. Some of the world's biggest languages support object-oriented programming, including Java, C++, Python, and JavaScript.
However, OOP is not the best solution for all development problems. This is precisely where functional programming comes into play.
What is functional programming?
Functional programming is a paradigm through which developers write programs using a combination of pure functions, which are designed in such a way that they have no side effects (more on this later).
If the word “functions” reminds you of mathematics, it is because functional programming evolved from lambda calculus, a formal system for expressing calculations based on the abstraction of functions.
It's worth noting that functional programming is part of a broader programming category called “declarative programming” (as opposed to “imperative programming,” which is the most widely supported paradigm in programming).
- Declarative programming occurs when the code tells the program what the result will be, without telling it how to achieve those results.
- Imperative programming is when the code tells the program exactly what to do at all times (in other words, the code gives the program the instructions it needs to execute to achieve a specific result).
According to this classification, functional programming is declarative because it uses mathematical functions that do not care about how the language or program performs the task. However, it is important to mention that the most popular languages tend to be multiparadigm. The few specialized functional languages include Haskell, Scala, and Ocaml.
Functional Programming Concepts
To really understand what functional programming is all about, it is important to define some of its key concepts. We've already mentioned pure functions, but we also need to delve into terms like “recursion” and “immutability” to fully understand what functional programming can offer.
Pure Functions
Functional programs do not use just any function, but rather pure functions. A mathematical function is considered pure if it meets 2 criteria:
- It has referential transparency, which means that a specific input will always provide the same output.
- They have no side effects, meaning they perform actions that are unrelated to their results (like manipulating an external variable or printing a value).
Pure functions work independently of everything outside them, especially states. This means they do not depend on external variables.
Recursion
Because functional programming uses pure functions (which have no side effects), it cannot use popular iterative techniques like for or while loops. This is because using them would mean using mutable states – which are side effects. Therefore, to perform iterative tasks, functional languages use recursion.
In recursion, a function does not need states because it employs ready-only arguments and write-only return values. Thus, recursion has a function executed repeatedly until it reaches its base case.
First-class functions
In functional programming, first-class functions can be used like any other value. In other words, you can use first-class functions to create arrays of functions, use them as arguments to other functions, and even store them in variables for later use. This is necessary for ensemble programs in functional programming.
Higher order functions
Higher order functions are also essential for building programs in functional programming. These functions are closely related to first-class functions in that they both allow functions as arguments and results of other functions. But they are not the same, because higher-order functions take at least one first-class function as a parameter (or return a first-class function as a result), while first-class functions are treated as variables or objects.
Immutability
Functional programming relies on immutable objects to accomplish tasks. An immutable object is one whose state cannot be modified after its creation – it will forever remain in the state in which you constructed it. The important thing about this is that immutable objects do not generate side effects (that is, they do not change state over time). As they have no side effects, they are perfect for functional programming.
Immutability is critical to functional programming because it makes code simple, testable, and ready for multithreaded systems. This is because objects don't change over time, which means you can easily identify bug-causing ones or use them in any thread you want without having to worry about breaking other threads.
Benefits of Functional Programming
Even when object-oriented programming is highly popular, developers know that functional programming can give them advantages that no other paradigm could provide them. Some of the most notable include:
Programming best practices
Functional programming is very rigid in the way it works, which translates into a number of restrictions that force developers to code in very specific ways. In other words, functional programming encourages engineers to adopt good programming practices, such as using modularity or building programs with small components.
Clean code
Given that functional programming combines pure functions that are organized components that work with each other, the code ends up being quite clean. Everything does exactly what it needs to do, which in turn helps with organizing the codebase.
Modular code
We mention modularity as a best practice primarily because modular code leads to small functions that can be reused over and over with the belief that they will not change how they work or will not be affected by changes to other functions.
Robust programs
Functional programs can be more robust than other applications (especially from a mathematical point of view). This is because functional apps don't have as many moving parts (like mutable variables and hidden states) as other apps. This means that functional applications are less complex, which makes them more efficient.
Easy debugging
Because data and function values are immutable, you will always know what is what and who does what in functional programming. This means you can easily identify the module responsible for any issues you may encounter while debugging.
Computational efficiency
Using functions allows you to distribute them across multiple cores without worrying about multithreading programs. This can help you harness the power of these cores, which in turn can provide more computational efficiency for your programs.
The best thing about functional programming is that you don't need to abandon other programming paradigms to enjoy all these benefits. This is because functional principles are present in many non-functional languages, which makes it easier to take advantage of these advantages. Of course, you can always opt for a purely functional language to increase the benefits, but in practice, it is quite pointless to do so.
Functional Programming Questions
Although proponents of functional programming might have you believe that it is a perfect paradigm, the reality is that it has some problems. Firstly, pure functional programming is very difficult to find in practice, mainly because developers often have to resort to other paradigms to get their programs to do what they want. And then, there are some other problems, including:
Easy to break
Functional programming is so elaborate that developers have to tiptoe around when using it. This is because they always need to meet strict functional requirements. If they don't, the app crashes and doesn't work.
Code can be difficult to read
Although functional programming offers clean and organized code, it also works at such a high level of abstraction that it can be very difficult to decipher what a functional code is actually trying to achieve. This is especially true for projects that rely heavily on functional programming libraries and dot-free syntax.
Steep learning curve
Because functional programming is deeply rooted in mathematics, it can be challenging for uninitiated developers to have a good understanding of its conventions and practices. Furthermore, functional programming involves highly specific terminology (such as “pure functions” and “referential transparency”) that requires a lot of study to fully understand.
Complicated function integration
Building specific features through pure functions can be an easy task once you understand how to do it. However, integrating pure functions into a larger application is always a challenge. This is because you need to figure out ways in which these pure functions (without side effects) can collaborate with each other to achieve the desired result (which can be very complicated, given the reluctance of pure functional programs to work with I/O components) .
Lack of recursive loops
While and for loops are so popular for a reason: they greatly simplify iterative tasks. But because functional programming doesn't use them, developers have to resort to pure recursion, which many people don't consider completely natural.
When to use functional programming
As with any programming paradigm, any expert will tell you that you can use functional programming for just about anything you want. And this is true to a certain extent! In fact, functional programming is so rigid and its programs are so robust that anyone working on a project with zero tolerance for bugs should use it. Arguably, all projects could benefit from the best possible quality, but using the functional approach also means the development team takes a long time to get the application right.
Given the above, it is important to choose the right projects to use functional programming. Given the lack of bug tolerance, this paradigm is perfect for systems for which a bug could have disastrous consequences, such as navigation systems or financial platforms.
Generally speaking, functional programming is great for projects that fall into some of the following groups:
- Applications that require solving complex mathematical problems
- Projects with tasks that need to process native and formal languages (parsers, DSLs, analysis and code generation)
- Systems that require high levels of concurrency and parallelism
- Platforms with complex data structures
- Data flow programming
- Projects that require extreme precision and correctness
It is important to say one last thing about the use of functional programming. While you can definitely handle most projects with it, you should study the feasibility of doing so (even for the tasks mentioned above). Why? Because there are other factors that come into play when selecting a programming paradigm, including infrastructure, libraries and resources, maintenance, and developers with knowledge of that paradigm.
Source: BairesDev