Paul Louth had a great development team at Meddbase, the healthcare software company he founded in 2005. But as the company grew, so did their bug count. That’s expected, up to a point. More code and more features mean more defects. But the defect rate was growing faster than Louth expected.
“We were seeing more and more of the same types of bugs,” Louth says. “It was clear that there was an issue, but it wasn’t clear what it was. My instinct told me the problem was complexity.”
Louth and company used C#, which had just happened to add support for Language Integrated Query (LINQ), an alternate language for querying data sources ranging from databases to objects within codebases. LINQ introduced ways to apply the programming paradigm known as “functional programming” to C# code, and learning about how LINQ worked led him to learn more about functional programming in general.
Functions—chunks of code that can complete a given task again and again within a program, without the need to re-write the code over and over—are a standard part of programming. For example, you could write a function that returns the circumference of a circle when given its diameter, or a function that returns an astrological sign based on someone’s date of birth. But as Cassidy Williams writes in her guide to functional programming for The ReadME Project, functional programming is about more than just writing functions. It’s a paradigm that emphasizes writing programs using “pure functions”: stateless functions that always return the same value when given the same input and produce no “side effects” when returning their output. In other words, pure functions don’t change any existing data or modify other parts of an application’s logic. If our circumference function changed a global variable, for example, it wouldn’t be pure.
In functional programming, data is generally treated as immutable. For example, instead of modifying the contents of an array, a program written in a functional style will produce a copy of the array with the new changes made. Unlike object-oriented programming, where data structures and logic are entwined, functional programming emphasizes a separation of data and logic.
“When you think about well-structured software, you might think about software that’s easy to write, easy to debug, and where a lot of it is reusable,” writes Williams, head of developer experience at Remote, a company that uses the functional programming language Elixir for their back-end software. “That’s functional programming in a nutshell!”
Functional programming can be a boon for growing teams working with large codebases. By writing code with few side effects and immutable data structures, it’s less likely that a programmer working on one part of a codebase will break a feature another programmer is working on. And it can be easier to track down bugs since, as Williams writes, there are fewer places in the code where changes can take place.
As Louth delved deeper into the world of functional programming, he realized the approach could help solve some of the problems that large-scale, object-oriented codebases presented, such as those his team was starting to run into at Meddbase. He wasn’t alone. Interest in functional programming has exploded over the past decade as more and more developers and organizations look for ways to tame complexity and build more secure, robust software.
“Once functional programming really clicked in my brain, I was like ‘why do it any other way?’” says Williams, who fell in love with functional programming during a college class that used the LISP programming language dialects Scheme and Racket. “Object-oriented programming isn’t bad, it still runs a lot of the world. But a lot of developers become evangelical about functional programming once they start working with it.”
Sure, object-oriented and imperative programming remain the dominant paradigms for modern software development, and “pure” functional programming languages like Haskell and Elm are relatively rare in production codebases. But as programming languages expand support for functional programming methods and new frameworks embrace alternative approaches to software development, functional programming is quickly finding its way into a growing number of codebases through a variety of different avenues.
“Once functional programming really clicked in my brain, I was like ‘why do it any other way?’”
Rise of functional programming in mainstream languages
Functional programming’s origins stretch back to the early days of programming languages with the creation of LISP in the late 1950s. But while LISP and its dialects like Common LISP and Scheme retained a devoted following for decades, object-oriented programming eclipsed functional programming in the 1970s and 1980s.
By 2010, interest in functional programming came roaring back as developers found themselves working with ever-larger codebases, and teams faced the same problems as Louth and his development team. Scala and Clojure brought functional programming to the Java Virtual Machine, while F# brought the paradigm to .NET. Twitter migrated much of their code to Scala, and Facebook deployed venerable functional languages like Haskell and Erlang to solve specific problems.
In 2014, Apple introduced Swift, a new, multi-paradigm language that includes robust support for functional programming. The launch proved that functional programming support had become a must-have feature of new programming languages, just as support for object-oriented programming already was. In a 2019 talk titled “Why Isn’t Functional Programming the Norm?” Feldman concluded that the functional style of programming is indeed becoming the norm. Or at least part of the norm. “Something has changed,” he said. “This style is not something people were considering a positive thing in the 90s, and the idea that it is a positive has become sort of mainstream now.”
The level and nature of support for functional programming vary by language, but one particular feature has become standard. “The thing that has seemingly ‘won,’ so to speak, is higher-order functions—functions that accept or return another function,” says Tobias Pfeiffer, a back-end engineer at Remote. “This lets you create blocks of functions to elegantly solve problems.”
Support for higher-order functions has made it possible for developers to bring functional programming into their codebases without re-architecting existing applications. When Louth and his team decided to shift to a functional programming model, they settled on using F# for new code in existing applications, since it provides robust functional support while maintaining compatibility with the .NET platform. For existing C# code, they employed the language’s own support for functional programming, along with language-ext, Louth’s own open source framework for functional C#.
“A full rewrite wasn’t an option,” Louth says. “So when we add new features to existing code, we write it following a functional programming paradigm.”
A bigger issue for those mixing paradigms is that you can’t expect the same guarantees you might receive from pure functions if your code includes other programming styles. “If you’re writing code that can have side effects, it’s not functional anymore,” Williams says. “You might be able to rely on parts of that code base. I’ve made various functions that are very modular, so that nothing touches them.”
Working with strictly functional programming languages makes it harder to accidentally introduce side effects into your code. “The key thing about writing functional programming in something like C# is that you have to be careful because you can take shortcuts and then you’ve got the exact sort of mess you would have if you weren’t using functional programming at all,” Louth says. “Haskell enforces the functional style, it encourages you to get it right the first time.” That’s why Louth and his team are using Haskell for some of their entirely new projects.
But purity is often in the eye of the beholder. “LISP is the original functional programming language and it has mutable data structures,” says Pfeiffer. Even if your language is purely functional and uses immutable data structures, impurity is introduced the moment you include user input. While user input is considered an “impure” element, most programs wouldn’t be very useful without it, so purity only goes so far. Haskell manages user input and other unavoidable “impurities” by keeping them clearly labeled and discrete, but different languages have different boundaries.
Expanding the functional mindset
Even though functional programming is now available in most major programming languages, developers aren’t necessarily taking advantage of those features. The functional programming approach requires a substantially different way of thinking about programming than imperative or object-oriented programming. “What blew my mind was that you couldn’t do traditional data structures in functional programming,” Williams says. “I didn’t have to make an object for a tree, I could just do it. It feels like it shouldn’t work, but it does.”
For example, the recently-introduced React Hooks are a set of functions that help developers manage state and side effects, while Redux—a popular library for managing state often used with React and other front-end libraries—takes a largely functional approach.
Meanwhile, Gonzalez sees functional programming becoming more mainstream through still another avenue: domain-specific languages. Being really good at one thing is often an easier route to adoption. For example, the Nix package management system’s expression language. “It’s even more purely functional than Haskell is, in the sense that it is even harder to write with side effects or mutability,” she says. Nix won’t be useful for, say, building a web server. It’s intended for only one purpose: building packages. But because it’s built for that specific task, developers who need a build tool might use it even if they wouldn’t otherwise try a functional programming language. “I think as time goes on you’ll see fewer general-purpose languages and more specialized languages,” she says. “Many of those will be functional.”
“React encourages people to think in a more functional way, even if it isn’t purely functional."
Building the future of functional programming
Like so much else in software development, functional programming’s future largely depends on the open source communities built around functional languages and concepts. For example, one barrier to pure functional programming adoption is simply that there are far more libraries written for object-oriented or imperative programming. From mathematics and scientific computing packages for Python to web frameworks for Node.js, it makes little sense for individuals to ignore all of this existing code that solves common problems just because it’s not written in a functional style. This is something Louth sometimes experiences with Haskell. “It has a fairly mature ecosystem, but it’s pockmarked by abandoned or unprofessional libraries,” he says.
To overcome that obstacle, the functional programming community will have to come together to create new libraries that make it easier for developers to choose functional programming over more side effect–prone approaches.
From the language-ext library to Redux to Nix, that work is already in progress.