Chapter 1. What Is Functional Programming?
Functional programming? Functors? Monoids? Monads? “I’m not a mathematician!” you might say. How can I learn these esoteric concepts? And why would I want to? These concerns are totally understandable. But the truth is you don’t need to be a mathematician to be a functional programmer.
The fundamental concepts of FP are easy to understand when presented in a clear, straightforward way. And that is what this book is about. Making FP understandable and practical. In particular, I will teach you how to think like a functional programmer. But why would you want to learn FP?
Picture this. It’s 10 p.m. and you are totally stuck while trying to fix a bug in a program you need to submit in the morning. The problem seems to be centered around a variable called ratio. The problem is that depending on the state of the system you are modeling, the variable ratio keeps changing. Your frustration builds. Or you have a deadline at work and there is an elusive bug in your microservice that you are chasing down. The problem seems to be in two nested for loops in which variables are modified in a fairly complex way. The logic is complex and you don’t quite see the solution. If only there were a way to write programs in a way in which the value of variables would not change! FP to the rescue.
Note
Variables whose values change often are a considerable source of bugs in programs. It can be difficult to keep track of the value of the variable since it can change at any moment.
So, what is FP? What makes one language functional and another not functional? The truth is that to some extent it is a matter of degree. You don’t have to follow every principle that falls under the heading of FP. Some people will try to follow all of them, and others will pick and choose. It is totally up to you. FP is a paradigm, an approach to programming, a way of breaking up the world and putting it back together in code. It involves both how we organize that piece of the world we are modeling and how we organize and structure the code.
To better describe the essence of FP, let us begin by contrasting it with imperative programming and object-oriented programming (OOP). There are others, such as logic programming, but the three mentioned are by far the most popular.
Imperative is what you might think of as plain old programming. It’s what programming was before OOP and FP. In imperative programming, you write functions or procedures, use for
and while
loops, and mutate state often. Languages like C or Pascal are typical imperative programming languages. Then there is OOP. Currently the most popular paradigm, OOP is a process of modeling the world as a collection of objects. Each object has state and methods, which are operations representing behaviors specific and relevant to that object. As the program runs, the state of the objects changes. The benefits of this approach include encapsulation, which means the state and methods that belong to an object exist, at the code level, within the object. This is a much better idea than letting the state be scattered all throughout the code because managing mutable state is just plain difficult. You have multiple variables and their values are changing. The approach of FP is to acknowledge this and attempt to minimize, if not erradicate, changing state altogether.
Note
Eradicating changing state, rather than attempting to manage it, is a fundamental principal in FP.
Ultimately, it is not always possible to avoid having mutable state, so the standard FP approach is to isolate the part of the code that mutates state. When we cannot eradicate all changing state, we can at least localize the code with the changing state into one place.
Immutability
The single most important aspect of FP is immutability. Generally speaking, this means a lack of change. Something is considered immutable if we cannot modify it in some way. In FP, this means a few things. Once a variable is set, its value cannot be changed. If x = 3 at the beginning of a program, it has that value for the remainder of the program. Does that mean that if a program is written in a functional style, and a person’s age changes, this change cannot be modeled? Of course not. That would be absurd. There are techniques like efficient copying that allow us to manipulate our code without ever mutating state. Consider the following simple for
loop in Java that prints the numbers 0 to 99.
Java
for
(
int
i
=
0
;
i
<
100
;
i
++)
{
System
.
out
.
println
(
i
);
}
This type of code occurs all the time. You might wonder how we could possibly express this in an immutable way. It seems that the essence of this code is the changing of the value of the variable i. A common approach in FP is to use recursive functions; a recursive function is one that calls itself. In the case of the preceding code, you can put the code in a function and then call the function on the next value of i in each iteration. It might look something like the following:
Java
void
f
(
int
i
)
{
if
(
i
>
99
)
{
return
;
}
else
{
System
.
out
.
println
(
i
)
return
f
(
i
+
1
)
}
}
f
(
0
)
Now this code is a little longer, but it does not mutate any state. If you know a little about FP, you might know that the return type void
is a sure giveaway that there will be side effects.1 A side effect is anything that affects the program outside of the function. Things like writing to a file, throwing an exception, or modifying a global variable. The earlier code example is meant to show a single way of avoiding the mutation of state. You have probably been mutating state your whole programming career and it likely seems indispensable. But remember two things:
-
It feels very natural to mutate state
-
Mutating state is a major cause of code complexity
The good news is that with practice, the FP way will feel just as natural.
Let us consider another technique for avoiding the mutation of state. Imagine you have an object with a property or field that changes. The question here is how to model this situation without mutating a variable in the code. Let us consider a Java example first.
Java
public
class
Person
{
private
final
String
name
;
private
final
int
age
;
public
Person
(
String
name
,
int
age
)
{
this
.
name
=
name
;
this
.
age
=
age
;
}
public
static
void
main
(
String
[
]
args
)
{
Person
person
=
new
Person
(
"Carl"
,
32
)
;
//A year passes
Person
changedPerson
=
new
Person
(
"Carl"
,
33
)
;
System
.
out
.
println
(
changedPerson
)
;
}
}
Instead of modifying the value of age in the
Person
object, we create a new object and initialize the new age value in the constructor.
Let us now look at an example in Python.
Python
class
Person
:
def
__init__
(
self
,
name
,
age
):
self
.
name
=
name
self
.
age
=
age
def
main
():
person
=
Person
(
"John"
,
22
)
#One year later
changedPerson
=
Person
(
"John"
,
23
)
One year passes and we need the Person
object to reflect this. But we can’t modify the value age. So we create another immutable object with the age variable initialized to 23
.
Now let’s look at an example in Scala.
Scala
case
class
Person
(
name
:
String
,
age
:
Int
)
val
person
=
Person
(
"Katherine"
,
25
)
val
changedPerson
=
person
.
copy
(
age
=
26
)
Create an instance of the class.
This line makes a new instance of
Person
and initializes age to26
. No state has been mutated.
Immutability is one of the most important aspects of FP. Having a lot of mutable state in a program results in a lot of bugs. It’s simply not easy to keep track of all the changing values. Here, we have seen some examples of how to get around the apparent need to mutate state. It takes some getting used to but with a little practice, using these techniques may even start to seem natural.
Referential Transparency
The next crucial component of FP is referential transparency. We say an expression is referentially transparent if we can replace it with its value anywhere in the code. You might think, upon first hearing about this, that you can always do this. Let us consider a simple example of a nonreferentially transparent function.
Java
today
()
If I call this function, get May 29th, 2021, replace its body with this value, and then call it tomorrow, I will get the wrong answer. So the today function is not referentially transparent.
Here are a couple more examples of nonreferential transparency:
-
A function that returns a random number. Obviously you can’t replace the body of the function with a value you get when you call it once.
-
A function that throws an exception. Exceptions are generally avoided in FP. I will come back to this later.
It probably seems that if we throw out all nonreferentially transparent functions (and that is what we will aim for), that we will lose some valuable capabilities—that perhaps we will be unable to express certain useful things. Rest assured, there are functional ways of expressing these things.
A related concept that you will see in writings about FP is purity. Unfortunately, there is some confusion in the literature about the relationship between purity and referential transparency and not everybody agrees on the meanings of these terms. Generally, a function is said to be pure if it has no side effects and for a given input, always returns the same output. This basically means that if the input is x and the output is y, no matter how many times you call the function with x as the input parameter, the function will return y. A side effect is anything that happens outside of the context of the function. Writing to a file and throwing an exception are two examples of side effects. Forget for the moment that we need to write to files (though arguably we don’t need to throw exceptions), and think how nice it would be if every time we call a function with the same input parameters, we get the same output and nothing outside of the function is changed. That is something we enjoy in FP.
Note
In FP, we strive to use only pure functions. That is, functions that have no side effects and have the property that if you supply the same input, you get the same output.
Because different people have different views on this, and because the differences between referential transparency and purity are subtle, I will treat the two terms as synonymous.
Now, I said you don’t have to be a mathematician to write functional programs, and you don’t. But FP does come from mathematics. It comes from two fields, actually, lambda calculus and category theory. Category theory has much to do with functions. And in mathematics, functions are pure. When a programmer looks at an expression like x = x + 1, they say, “Ah, increment the variable.” When a mathematician looks at x = x + 1, they say, “No, it doesn’t.”2
Now what would an impure function look like?
Scala
object
Main
extends
App
{
def
impureFunction
(
x
:
Int
):
Int
=
{
import
scala
.
util
.
Random
return
Random
.
nextInt
(
100
)
+
x
}
println
(
impureFunction
(
5
))
println
(
impureFunction
(
8
))
}
The two function calls will very likely return different output values for the same input value. This function is not pure. We have said mathematical functions are pure. Well, programming has gained quite a lot from this mathematical approach. Functional programs are clean, pure, and elegant. FP style may take a little bit of getting used to at first, but as we gradually move through the basic ideas of FP in this book, you will begin thinking like a functional programmer. Your functions will be pure and your code will be clean.
Note
The biggest benefit, however, of writing functional programs is that you will have a much stronger expectation that your programs will be correct.
Let me make an important point here. We can’t define FP with negation; we can’t say it’s the same as ordinary programming except that we leave out this, that, and the other thing. The hard part, the part accomplished by the many creators of FP, is how to express everything we need in a functional way.
Higher Order Functions
FP is all about functions. What we want, in a FP language, is the ability to treat functions as first-class citizens. This means we should be able to pass them as function parameters and return them from functions as return values. Let’s discuss why higher order functions are an important part of FP. One key goal in FP is to get to the heart of the matter. This means we need to be able to express concepts concisely in our language. If we want to square every integer in a list, for example, we shouldn’t have to loop through the list and modify each number by squaring it. We should be able simply to directly apply a square
function to every element of the list simultaneously. The map
function, in many languages, allows us to do this. It allows us to work at a higher level of abstraction. That higher level corresponds to a higher order function. We will see this as a major theme as we proceed.
An imperative approach:
Python
def
square
(
nums
):
squared
=
[]
for
i
in
nums
:
squared
.
append
(
i
*
i
)
return
squared
A functional approach:
Python
def
square
(
nums
):
return
map
(
lambda
n
:
n
*
n
,
nums
)
As shown in the Appendix, lambda
is a way of creating an anonymous function; that is, creating a function without a name, or on the fly, as it were. The map
function acts on members of a list and, all at once, applies it to all the elements of the list.
Lazy Evaluation
Another component of FP is lazy evaluation. This simply means an expression is not evaluated until it is needed. This is not, strictly speaking, necessary for a language to be functional but often languages that are by nature more functional, tend to be lazy. Haskell, for example, is lazy by default and can be thought of as the archetypical FP language. It was designed by a committee of academics and makes no compromises when it comes to functional principles. Most popular languages are not lazy, though, and use what is called eager evaluation. This means an expression is evaluated every time it is encountered. As you’ll see in the following example, there are two benefits of lazy evaluation:
Imagine you want to define your own if
statement. Let’s call the function myIf
. You might want to add a logging line to every if
statement, for example. If you try the following, you will encounter a problem.
Scala
def
myIf
(
condition
:
Boolean
,
thenAction
:
Unit
,
elseAction
:
Unit
):
Unit
=
if
(
condition
)
thenAction
else
elseAction
Can you see the problem with this definition? With eager evaluation, which most common languages have, when the function is called, the first thing that happens is that all of the parameters are evaluated. So in the case of myIf
, both the thenAction
and the elseAction
will be evaluated when you want only one of them to be evaluated, depending on the condition variable. However, with lazy evaluation, this would work. In this and related cases, it would allow you to write your own control statements.
Note
With eager evaluation, function parameters are evaluated as soon as the function is called. With lazy evaluation, they are not evaluated until they are needed.
Another benefit is performance improvement in certain situations. Since the lazy code is evaluated only when it is needed, it is often the case that it is actually evaluated less than it would be in the eager evaluation case. This can speed up the program.
In Scala, we can use call by name parameters. In the following code, thenAction
and elseAction
are evaluated only when they are needed. That is, they are evaluated lazily. The following will work as expected.
Scala
def
myIf
(
condition
:
Boolean
,
thenAction
:
Unit
,
elseAction
:
Unit
):
Unit
=
if
(
condition
)
thenAction
else
elseAction
With lazy evaluation, we can create our own versions of operators like if
or while
.
In conclusion:
Thinking Like a Functional Programmer
In this book, we will focus on how to think like a functional programmer. While there are a wide range of approaches to FP, some concepts are universal across these approaches. Functional programmers don’t mutate state, for example. That is, once a variable has been set, it is never changed. Also, functional programmers tend to use lots of higer order functions. These are functions that take other functions as parameters and/or return a function as a return value.
Note
To know how a functional programmer really thinks involves knowing a set of idioms, or patterns that promote functional code.
It’s all well and good for me to tell you not to mutate your variables, but unless you know how to work around this, implementing immutability may not make any sense. In other words, patterns are an important part of FP.
You may have heard that functional programmers don’t really care as much about patterns as object-oriented programmers do. This is a misconception. What is true is that the term pattern, in the context of FP, refers to something different than the Gang of Four patterns.3 The Gang of Four patterns (e.g., prototype, proxy, and flyweight patterns) were developed in the context of OOP. These can largely be implemented in a functional style and are useful in the design of programs, but there is nothing particularly functional about this type of pattern. One might say they are functional-neutral. While the Gang of Four patterns are functional-neutral, there is another category of software patterns that is explicitly functional. These patterns, such as the functor and monad patterns, derive from ideas in category theory. We’ll look at these in greater detail in Chapter 3.
The Benefits of FP
The benefits of FP are becoming clear. It aids us in our quest for bug-free code. Or as close to bug-free code as is possible. And how does it do this? By rewiring our brains so that we no longer see the world as a mass of objects, each with its own changing state and processes that transform that state. With a change of perspective, we can identify state as the culprit.
Warning
When state changes, we need to keep track of it, which means there is more complexity to manage, and more bugs. This is problem FP solves.
Human beings can handle only so much complexity before we start writing code that isn’t quite correct. “But wait,” you say, “The world is made up of objects. And those objects have state, and that state changes over time! So we are right to model the world this way. That’s exactly how the world is!” But that doesn’t mean we can’t begin to see (and model) the world in more functional terms. For now, the important thing is to realize that writing bug-free software is not something we really know how to do. I knew a computer science professor who once started off his introduction to programming class with the sentence: “The human race does not yet know how to program.”
A bit dramatic, perhaps, but true. Projects typically come in above budget and take much longer than predicted. The reason is complexity. Programming is the art, science, and engineering of managing complexity. FP brings with it tools we can use in an attempt to restrain and control this complexity. Tools like immutability, referential transparency, and higher order functions, to name a few. Master these tools, and your code will be less buggy.
FP Can Improve Productivity
So FP is a programming paradigm. What other paradigms are there?
The most popular is arguably OOP.4 If you have programmed in Java, C#, C++, or Python, for example, you are probably familiar with this method of programming. In this case, the world is modeled as a collection of objects each with its own state and its own behavior. OOP has many benefits, including abstraction, encapsulation, and inheritance. But even with these benefits, code often suffers from coming in overbudget and overtime. Before FP and OOP became popular, there was imperative programming. On the surface, imperative programming resembles FP a bit. are the main software abstraction used in imperative programming; there are no objects or classes. But on closer inspection, one sees that state is mutable, functions are not referentially transparent, and imperative languages didn’t necessarily have higher order functions. C and Pascal are two examples of imperative programming languages.
You could argue that the best programmers will produce better code no matter what paradigm they use—this is probably true. The question is: if we have two developers of equal skill, one working with an object-oriented approach and the other working with a functional approach, who will be more productive? The clarity, power, and higher level of abstraction will allow the functional programmer to produce more correct code, faster.5
FP Is Fun!
There is another reason to program with FP. This is perhaps the most important reason yet.
Note
FP is fun, and for deep reasons.
FP lets you get to the heart of the matter. It lets you cut to the chase and spend more time coding on the subject matter. It is at a sufficiently high level of abstraction that it feels as if you are manipulating important, relevant concepts instead of drudging through low-level details that closely model what a machine does.
The history of programming languages, from one perspective, is largely a story of ever increasing abstraction level. The higher the level, the easier it is to avoid manipulating masses of detail. FP shows us that abstraction does not have to be difficult. Yes, there is a learning curve, but it is possible to shorten this curve by making the change little by little. If you currently code in Java, JavaScript, or Python, for example, it is possible to gradually include idioms, structures, and practices that will make your code more functional and before you know it, you will start to naturally rely on functional idioms for the power and tendency toward simplification that they provide. If you read this book carefully, study the examples, and start to incorporate some functional ideas into your code, you will soon see great benefits.
Note
You may even decide you want to investigate some programming languages that provide more support for the functional paradigm. Languages such as Scala, Clojure, F#, and Haskell, to name a few.
Scala
A note about the examples in this book. I will be providing examples in various languages. This is to demonstrate the way different languages implement functional ideas. While it is possible to write functional code in various languages (with varying levels of ease depending on the language), some languages make it a lot easier to do and are generally more powerful because of this. One such language is Scala.
There are two things I want to say about Scala:
-
Scala is a concise language
-
Scala is conducive to writing functional code
So while we will be including examples in Java, Python, and C#, most of the examples will be in Scala. For a short introduction to Scala, see the Appendix. Especially when describing category theory, Scala is a good choice. I would argue that a language like Scala allows for more concise functional constructs and in many cases it may drive a point home to show how to do it in Scala. There are other languages I could have used for this purpose—other languages that are functional to one degree or another—Haskell (which is exceedingly functional), Clojure, and Meta Language (ML) for example. I simply felt that the clarity of Scala and the ease with which one can write functional code made it a good choice for many of the examples in this book.
Note
If you have been using Java and you are interested in FP, you may want to try out Scala. I would argue that, especially for greenfield projects, you might find it useful, productive, and fun.
I mentioned that this book will contain examples in Scala, Java, Python, and C#. But as we get deeper into function concepts, it will be more useful to craft our examples in Scala. Especially when we learn about some of the abstract concepts in FP. There are different degrees of FP. Some languages are partly functional and others are fully or purely functional, as we say.
In Table 1-1, you’ll see a chart comparing the languages used in examples throughout the book. I have also included Haskell, even though I don’t use Haskell examples, because it is an example of a purely functional programming language and it is good as a means of comparison.
Scala | Java | Python | C# | Haskella | |
---|---|---|---|---|---|
Supports functional |
YES |
PARTLY |
PARTLY |
PARTLY |
YES |
Supports purely functional |
NO |
NO |
NO |
NO |
YES |
Supports OOP |
YES |
YES |
YES |
YES |
PARTLY |
a Haskell was created to be a purely functional language. For example, once you set the value of a variable, you cannot change it. It has many other functional features, many of which come from category theory. We will see category theory in Chapter 3. |
Conclusion
In this chapter we have looked broadly at the major features that make up an FP language. As the book progresses, we will get deeper into these features and how the fundamental patterns of FP are reflected in them.
1 In FP, all functions should return a value. void
is a sure sign of side effects.
2 This is meant to be a joke, but I’ve experienced these reactions firsthand.
3 You can read more about the Gang of Four in Design Patterns by Erich Gamma et al. (Addison-Wesley).
4 While there is more OOP code in existence currently, there is clearly a move in the direction of FP among many developers. It remains to see how this will play out. Perhaps a hybrid approach that mixes both approaches will become the norm. Or perhaps FP will just continue to get more popular.
5 To be clear, this is my opinion.
Get Learning Functional Programming now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.