What Might Functional Programming Mean
and should we care?
Table of Contents
Introduction
There are many arguments about the virtues and vices of functional programming. But it can be difficult to find an agreed upon meaning of the phrase. Here, I want to explore some of the most common characteristics of functional programming that I've seen floating around, and discuss each of them in turn, with the ultimate question in mind: how much should we care about them? In particular, I want to see if there's really an argument to be made for preferring these characteristics absolutely over competing ones, if they are useful at all.
TLDR0
I want to know what functional programming is, and if it's a good thing.
Programming with Functions
One of the oldest and most popular understandings of functional programming is that it's, well, programming with functions! This is even the opening line of the Wikipedia article on functional programming:
In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.
—Wikipedia
Of course this statement is worthless on its own, as all computer programming "paradigms" with names involve applying and composing functions. Instead, the key idea is to treat functions as proper data objects. That is, as things that can be bound to variables, passed as arguments to other functions, and returned as values from other functions. This is called treating functions as first-class citizens, and is the first real innovation in the tradition that would come to be known as functional programming, when it was first introduced by Lisp at MIT starting in the late 1950s.
Functions as First-Class Citizens
However, treating functions as first class citizens, like "programming with functions", is too broad! There are two questions one could ask:
- Am I able to treat functions as first-class citizens?
- Do I want to treat functions as first-class citizens?
Power
For (1), the answer is entirely determined by the programming language one is using. Although it was a novelty 60 years ago, many programming languages which are widely considered to be not functional have support for first class functions. So while affirming (1) is almost certainly a necessary condition for functional programming, it is not sufficient.
Desire
Perhaps then functional programming is about absolutely affirming (2). To be doing functional programming is to be always either binding a function to a variable, or calling a function which returns a function, or passing a function in as an argument to another function, or some combination thereof. But this is of course ridiculous, as there are many other things you do while programming which involve none of these things, even in the most pure functional programming languages. The most obvious ones being either binding a non-function value to a variable, or returning a non-function value from a function.1
All that's left then is not to affirm (2) absolutely, but rather conditionally. That is, to be doing functional programming is to sometimes treat functions as values (including using a language which facilitates such). But this is another widely held belief, which is evidenced by the widespread adoption of the capability at the language level in more recent years, again even in languages which are considered to be not functional, and utilized by those who don't consider themselves to be doing anything like "functional programming".
TLDR1
Functional Programming as "treating functions as first class citizens" is at best something which is sometimes useful to do, and is a non-controversial practice.
No Side Effects
Perhaps the most popular (yet least interesting) description of functional programming is that it is programming without side effects. Here a "side effect", which should just be called an effect, is anything that a piece of code causes to happen. Since the point of writing computer programs is to cause computers to do things, trying to write a computer program which does nothing is all but nonsensical. I think that is enough that needs to be said about this idea, but since it is so widely discussed, I will say more.
Alternatives
There is one slightly more reasonable, and two much more reasonable, opinions which people often mean when they talk about no side effects:
- There are no references to side effects in the language specification
- An implementation of that language may perform side effects when it processes certain expressions
- Side effects are dangerous and so we should be careful about how we use them
- All data structures in the language are immutable
I will discuss (3) in the next section.
Language Specification
As for (1), it's hard for me to imagine why anyone would care about this. All programs are free from side effects in the abstract, and all programs have side effects in reality when run on a computer. The fact that a programming language spec doesn't allow you to express the idea of an effect doesn't change either of these facts. You still have to deal with side effects when you actually run it, and you can still ignore side effects when abstractly reasoning about it. There are perhaps some intellectually interesting properties of languages which don't reference side effects, but here I am discussing functional programming as a paradigm, not functional programming language theory.
Caution
Finally for (2), this seems very reasonable, but again has become a widely held belief amongst programmers in general, not just functional advocates. However here I do believe that there is still a gap, as the ramifications of undisciplined employment of effectful procedures is sometimes not fully appreciated by those with no exposure to functional programming.
TLDR2
Functional programming as "programming without side effects" is nonsense, but recognizing the dangers of side effects is helpful, which is mostly non-controversial.
Immutable Data
We have now arrived at what I consider to be the most reasonable and important sense in which functional programming is worth talking about. The absolutist version of the idea is simple: you should never mutate data. This usually means that the programming language itself should support this by offering no primitives for mutation, thus making all data structures immutable.
This is a genuinely controversial idea which actually has the promise of being worthwhile, as it is already the case in a few programming languages. The only widely used languages for general purpose programming with this quality that I am aware of are Haskell and Erlang/Elixir.
The most obvious benefit of this is that it is easier to reason about code when you don't have to worry about the "current" state of any internal data. Here is a quote from John Carmack in 2022:
When I'm sitting down to do what I consider serious programming, it's still in C++, and it's really kind of a C flavored C++ at that.
…
There is a lot of value [in functional programming] in the way you think about things… the value that comes out of not having this random mutable state that you kind of lose track of.—John Carmack
The Rub
There are minor potential downsides, such as performance, which I won't discuss here. Rather, I wish to discuss what I see as the only candidate for a fundamental downside.
When we write computer programs, we often want to model some part of the world. Since part of our goal when writing computer programs is always to change the state of the world, it often seems useful to reify those mutable parts of the world in our program. And the fact that they are mutable is not an incidental property we can ignore, but essential to the problem at hand.2
So it seems that we lose a kind of expressive power with immutable data structures. This is especially evident in areas such as graphical programming, where it can be difficult to imagine not modeling graphical objects as those with states that change over time. But even here there has been work to come up with functional paradigms, albeit with no obvious grand success of which I am aware.
TLDR3
Functional programming as "programming exclusively with immutable data" is an interesting and still yet-to-be widely accepted approach to programming which is worth talking about. The dangers of undisciplined data mutation is widely known and non-controversial.
Pure Functions
Finally we have by far the most complex idea in functional programming, yet also one of the most useless. The idea is that all functions you write/use should be "pure" functions. There are at least 4 different properties people tend to bring up when talking about pure functions:
- No side effects
- No mutation
- Only looks at arguments
- Same input <—> Same output
We have already discussed (1) and (2).
Only Arguments
(3) is terribly misleading, funnily because of not taking seriously the idea of functions as values. It is virtually impossible to write a function which only looks at its arguments. Consider the following function definition:
(define square (lambda (x) (* x x)))
There is a non-local reference here: *
!
Every function consists of references to non-local data.
What makes the above code actually "transparent" to reason about is lexical scoping.
That is, the fact that the value which is bound to *
within the scope of the function cannot be changed outside the scope of the function after its definition.
Note that immutability also makes a difference here, because it ensures that there is no time attached to this definition.
If there were mutation in the language of the above code, mutating the value of *
before the definition is made could cause confusion.
Input <—> Output
That leaves us with (4). But we obviously don't want all functions to return the same value given the same arguments + environment, because we would like to read in data from the outside world. So we can't take an absolutist stance to (4), only a conditional one. But that functions should generally return the same thing given the same arguments is widely agreed upon.
Therefore, talking about pure functions either reduces to talking about something else (like immutability or side effects), or is pointless/trivial.
TLDR4
Functional programming as "programming with pure functions" is a useless notion.
Conclusion
Most of the traditional qualities of functional programming have been adopted by programmers and programming languages which both functional and non-functional advocates alike agree are not functional in nature.
The only exception to this I see is the still seemingly contested approach to (im)mutable data. The idea that immutability is the core of functional programming goes back all the way to at least the 1970s3, and so has historical legitimacy as well. Therefore I believe that talking about functional programming as immutable programming is the most clear and sensible approach to the subject.
Bath Water
In addition to this, most if not all of the other ideas discussed in this article are related to each other. It is difficult to program with immutable data without first-class functions, and it is easier to control side effects with immutable data. Thus I don't think there is any need to censor speech about these other qualities in relation to functional programming. But, rather than talking about them as part of a constellation of functional qualities, we should instead speak of them as the branches of a tree rooted in the idea of immutability.
Loose Ends
Finally, there are a few other things people bring up when talking about functional programming, such as:
- Pattern Matching
- Recursion / Tail Call Optimization
- Lazy Evaluation
- Type Systems
and more. However, these are all potential concrete features of a programming language, not a programming paradigm. I would classify them similarly to everything which is not immutable data that we've already discussed: things which pair nicely with functional programming.
TLDR5
Functional programming is immutable programming, which makes it very cool 😎.
Footnotes:
We are definitely ignoring philosophical questions about identity and constructs like lambda encodings.
For a more in-depth discussion of these ideas, see Structure and Interpretations of Computer Programs chapter 3.
Again, see SICP.