Today’s business domains – from supply chain to telecommunications to banking – need software solutions for more complex problems. Functional programming (FP) is the answer by breaking down problems into roles and processes – each addressed by a specific function. Easy to understand and manage, FP is robust, scales well and can be used with complex applications.
Functions are composed or chained like mathematical functions, so are predictable and testable. When written with unit test cases these functions deliver results that match business use cases and organisational goals. The problems of code complexity and bloat, that John Backus addressed in his 1977 Turing award lecture, are solved by FP’s principles of immutability, pure functions and function composition – concepts from Lambda calculus that are still relevant today.
Complex applications for a complex world
Complex applications are written in multiple languages. A multi-language code complexity study found that “independently of the programming language, complex methods become more complex over time.”. The study found that conditionals were the main contributor to complexity (measured by cyclomatic complexity). While all programs became more complex over time, the programming language still had an effect, especially on complexity reduction during code maintenance. Additionally, C code had less refactoring and more complexity increases than other languages. Side effects from code and state management complexity can be order of evaluation errors, undefined behaviour and unspecified software behaviour that undermines reliability.
—> For high traffic applications like microservices, side effects can be business process and security vulnerabilities. Reliability is key, some services have millions of API calls per day. WhatsApp is a successful FP application written in Erlang, a purely functional language. FP’s advantages and lean nature allows the mega app to be maintained by a small team.
Immutability and side effects
—> Null references and bloated object size, found in imperative programming, are the sources of side effects and are eliminated in FP through immutability and strict typed data structures. To define a function in FP, as in mathematics, you need to specify the range of the function and explicitly consider the null reference so you can do robust mathematical testing. In functional and JVM languages when a function ends the function’s input is garbage collected. The argument value only exists for the duration of the function. This immutability can lead to more memory usage but at the cost of performance, reliability and maintenance.
—> Memoization is a technique to reduce memory usage where the result of an expensive function is cached so it can be reused instead of re-running the entire function or algorithm. In imperative programming the result is stored as data. In FP the result can be stored as a higher-order function. Since the output of a function in FP can be a function this becomes a very powerful tool to handle complexity and increase reliability.
Functional decomposition
—> FP leverages functional decomposition and currying to make software lean, reliable, and readable. Complex functions are broken down into smaller functions and further into granular functions. These functions are combined through function composition or currying, where a series of simple functions are chained together. Pure functions are robust, like mathematical functions. They are completely predictable. By making use of closure, associativity, and identities, FP constructs monoids and monads to control outputs and ensure reliable operator functions. Once the unit test cases that solve the business use cases are coded against these functions and the desired behavior is achieved, these functions never fail.
—> For example, suppose a business process or microservice method requires 100 functions, including external event requests like REST API calls. In that case, FP can structure the code so events are minimized and calculations, or pure functions, can be maximized. This means that a given 100-function program can be designed so that only five to ten of those functions are event functions that interact with external variables and that 90-95 percent of the program has 100 percent testable and reliable outputs.
F(x) = y; given x: F will always produce y.
—> Through monads and currying, a program’s surface area or contact points can be minimized to increase its reliability, resulting in fewer contingencies for complexity and side effects.
Integration
—> In modern programming languages like Scala, Clojure, Python, and Java, classes are mostly used as placeholders for functions. Since Java 8 and Python 3, tools and features for FP are available, such as functions being first-class citizens (they can be inputs and outputs of other data types). Classes are still fundamentally treated as objects and do not have FP rules enforced at the interpreter like purely functional languages do, and they are not side-effect-free. An advantage of classes is that they can aggregate related data and integrate with different programming paradigms. With data processing necessary for today’s complex business solutions, integration with tools like Apache Spark, a popular analytics engine for data processing, is essential.
—> To take advantage of FP, it’s essential for a development team to develop functional programs, requiring a change in the Devs’ mindset to solve problems through functional decomposition. The question is not if to use FP but where to use it. FP is a thoroughly tested paradigm for the future of computing, and it’s already here. Every domain can benefit from the higher predictability and reliability of its software. Hardware and network advances since Backus’s 1977 Turing award mean that FP is now a reliable solution to complexity. Functions that never fail are just a change in mindset away.