What is Functional Programming?
Functional Programming is a programming paradigm centered around the concept of writing software by composing pure functions, and avoiding shared state and mutable data. Its emphasis on predictability, fault tolerance, and high concurrency makes it especially useful in fields like communications and finance. Although functional programming has been around for many decades, it has recently gained broader adoption, and has influenced numerous programming languages, notably Elixir, which is a popular choice for fintech software development.
Where Did Functional Programming Come From?
Functional programming has its roots in the 1930s with the development of the lambda calculus by Alonzo Church. In lambda calculus, functions are the primary means of computation. This foundational idea would eventually become a cornerstone of modern programming languages, but the first languages to adopt it were not developed until decades after Church published his seminal work in 1936.
The Earliest Functional Programming Languages Were Late
LISP, developed by John McCarthy in 1958, is often recognized as the first functional programming language, even though it was initially created for artificial intelligence research and not explicitly as a “functional language.” ML, developed in the early 1970s, and Haskell, developed in the late 1980s, are notable examples of languages that were designed specifically with functional programming principles in mind. Erlang, also developed in the late 1980s, stands out for its adoption of functional programming concepts within the domain of telecommunications.
Overall, while the core ideas of functional programming were conceptualized with lambda calculus nearly a century ago, it took quite a while before these ideas were fully realized in the design of programming languages. During its early decades, functional programming remained primarily of academic interest, in contrast to the commercial prevalence of procedural languages.
A Growing Practical Need For Concurrency
Part of the reason why functional programming existed for such a long time before its widespread adoption was that it solved a problem for which there was not yet widespread need. Specifically, it offered a practical solution for high-concurrency systems, where many tasks are handled simultaneously; but the commercial demand just wasn’t there. The major high-concurrency systems that did exist were still analog, and there wasn’t a lot of concurrency in the computing world. Thus, the procedural approaches that dominated software development at the time worked fine.
However, as industries like telecommunications began to transition from analog to digital, there was suddenly a need for programmatic support for high-concurrency - and the available solutions were not adequate.
The Challenge of Concurrency Under Traditional Paradigms
Legacy programming languages, like C and Java, were not originally designed with concurrency in mind. Code was traditionally structured in a way where different parts of the software were highly interdependent. This meant that changes in one part of the software could have an unpredictable ripple effect on the whole system. In less complex systems, these effects were manageable. In more complex systems, concurrent operations inflated the unpredictability of interdependent architectures, making them unmanageable.
Functional Programming: A New Paradigm
Functional programming offered an alternative approach. Instead of producing an inscrutable web of interdependent code, functional programming simplified and reduced the dependencies within its architecture, resulting in more transparent and predictable software that could handle many simultaneous tasks.
Two key concepts contributed significantly to the ability of functional programming to achieve this: immutability and statelessness.
Immutability, as opposed to mutability
Traditional programming allows for broad mutability, meaning that a lot of things are allowed to change, or mutate, even crucial aspects of a system like the data structure.
Imagine a car that has no tire specifications. The car will perform better with matching tires, but without immutable specifications, each tire must be individually synchronized with the others. Otherwise, they can easily become mismatched, which can lead to uneven wear, poor handling, and risky driving conditions.
Now imagine a car that does have standard, immutable tire specifications. The specifications serve as a centralized source of truth that the tires can synchronize with, rather than having to individually synchronize with each other. Even if the manufacturer issues a repair recall that results in an update to the tire specifications, cars that have been repaired can use the new specifications while cars that have not yet been repaired can still use the old specifications. Either way, the presence of immutable specifications helps ensure that the tires are synchronized, and that the car ‘system’ remains stable.
Although all systems have some level of interdependence, functional programming shifts the nature of dependency from a more chaotic and less predictable system (where each tire requires individual synchronization) to a more organized and predictable system (where all tires conform to a standardized specification). In this way, immutability makes the dependencies within a system more straightforward, and therefore, more manageable.
Statelessness, as opposed to statefulness
Traditional programming often determines the behavior of a system based on the state of the system, or the state of parts of the system (the application state, data state, session state, etc). A side effect of this is that the system’s behavior becomes dependent on its memory of past behavior, and states must be actively managed.
Returning to the car example, this would be like a car without an auto setting for its headlights. The headlights always ‘remember’ their last setting, regardless of the current driving conditions, and the driver must manage the behavior of the headlights with manual controls. If the state of the manual controls is not properly managed, this can cause all sorts of problems. Forgetting to turn the headlights on while driving at night, for example, is pretty risky. On the other hand, forgetting to turn the headlights off while the car is parked in the driveway can affect other parts of the car, like the battery, causing the whole system to fail. Improper management of states, therefore, can degrade a stateful system’s performance and even destabilize the entire system itself.
What if we didn’t have to worry about having to manage states or the system remembering past states? What if the system could just forget the past, and figure out the correct behavior based purely on live input? In other words, what if we could have a stateless system? This would be like a car that does have an auto setting for its headlights. Instead of the behavior of the headlights depending on previous settings and active management, the headlights behave according to input received by the car’s sensors. This simplifies the headlight behavior, because it no longer has to be actively managed, and also makes the behavior more predictable. When it’s light out, the headlights are always off; when it’s dark out, the headlights are always on.
Another way of saying all this is that statelessness moves behavior away from high-maintenance states to simpler, composable functions. Functions determine behavior based on live input, not based on the historical baggage of states.
The Modern, Concurrent Landscape
There are many more aspects of functional programming that make it a really good solution for complicated, high-concurrency systems, but for this overview, immutability and statelessness represent two of the main philosophical cornerstones that make a lot of the difference.
With the ubiquity of mobile computing, functional programming has been experiencing a renaissance. Many legacy systems that digitized long ago have entered another paradigm shift to mobile, where constant user access produces higher volumes of simultaneous interactions. Not only does a commercial need exist, but modern languages, like Elixir, have made functional programming more accessible to developers. This shift to functional programming, nearly 100 years after its inception, is helping modern developers build a reliable, fault-tolerant future in an increasingly interconnected, real-time world.