Most of the discussions we have in the software industry today revolve around developer productivity: How do we make it possible for fewer developers to produce more software in less time? Reducing the upfront costs and delivering quickly is essentially the mantra of the startup world: Move fast and break things.
However, the vast majority of time in software development is not spent in the initial development phase. For any successful project, an overwhelming amount of time is spent on maintaining that software. To keep your customers happy, it’s vital to continue improving the software, fixing bugs and enhancing performance. If you are hamstrung on your ability to innovate, constantly fighting bugs and delivering an inferior user experience, your competitors will be able to outmaneuver you in the marketplace.
Functional programming is a significant paradigm shift in the software world over the past 10 years. Slowly but surely, it has moved from a niche feature of a few uncommonly used languages to a mainstay of even the most established languages. Unlike preceding paradigms, functional programming makes a focus of assisting in not just the productivity of developers, but of long-term software maintenance.
The bane of many programs, especially concurrent and network programs, is the fact that data changes in unexpected ways. Functional programming advocates keeping most of your data immutable. Once created, the data does not change. You can share this data with other parts of your program without fear of it being changed or invalidated.
Languages like Haskell and Rust make this a cornerstone of their implementation. C++ offers the ability to opt-in to immutability. Many Java coding guidelines recommend defaulting to immutable data when possible.
Classic programming involves instructing the computer which steps to take to solve a problem. For example, to add up the numbers in a list, an imperative programming approach might be:
This kind of imperative approach works, but doesn’t scale particularly well. As problems become more complex, the imperative approach requires ever more complicated solutions. It’s difficult to separate logical components into multiple separate loops without sacrificing performance. And in the era of multicore programming, creating a multithreaded solution requires significant expertise with safe thread handling.
In functional programming, the preference is a declarative approach. Summing up a list is typically done as:
This approach naturally translates into a multicore solution. Instead of each loop needing to handle the complexities of thread management, a library author can write a parallel fold once. The caller can then replace their non-parallel fold with a parallel fold and immediately gain the benefits of multicore.
By combining this approach with other declarative programming methods, like mapping, functional programming can express complex data pipeline operations as a composition of many individual, simpler components. This forms the core of such well-known systems as Google’s MapReduce.
For years, the industry debate around typed languages was usually between the C++ and Java families versus the Python and Ruby families. The former introduced some sanity checks at compile time in exchange for lots of ceremony with explicit type annotations. This improved code maintenance somewhat, at the cost of significant developer productivity. Python and Ruby, by contrast, skipped the type annotations entirely, leaving them as a runtime concern. This boosted productivity, at the cost of maintainability.
The functional world went a different way: strong, expressive type systems with type inference. Type inference avoided much of the boilerplate introduced by the C++-style of type systems, allowing productivity on a par with Python and Ruby. The strong type systems in functional languages allowed even more guarantees to be expressed in types, improving maintainability beyond the levels of C++ and Java.
These days, even dynamically typed languages like Python are beginning to introduce type systems due to the massive gains they are demonstrating. New languages like Rust are borrowing some of the most popular type system features from functional languages like Haskell and O’Caml: sum types, traits and more.
It’s important to note that you do not need to completely rewrite all of your software in a functional programming language to reap many of the benefits of functional programming. You can begin rolling out functional features in your existing software today with improvements to your internal coding guidelines. Focusing on some of the features above, and many of the other inspirations from functional programming, is a great start.
With the rise of microservices architectures, a hybrid deployment model may make a lot of sense. Oftentimes, offloading a particularly critical piece of business logic to a separate, well-tested functional programming codebase, connected via network APIs, can reduce the burden on the rest of your team and increase the stability of your software.