Hands-On Functional Programming with C++

4 (1 reviews total)
By Alexandru Bolboaca
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. An Introduction to Functional Programming

About this book

Functional programming enables you to divide your software into smaller, reusable components that are easy to write, debug, and maintain. Combined with the power of C++, you can develop scalable and functional applications for modern software requirements. This book will help you discover the functional features in C++ 17 and C++ 20 to build enterprise-level applications.

Starting with the fundamental building blocks of functional programming and how to use them in C++, you’ll explore functions, currying, and lambdas. As you advance, you’ll learn how to improve cohesion and delve into test-driven development, which will enable you in designing better software. In addition to this, the book covers architectural patterns such as event sourcing to help you get to grips with the importance of immutability for data storage. You’ll even understand how to “think in functions” and implement design patterns in a functional way.

By the end of this book, you’ll be able to write faster and cleaner production code in C++ with the help of functional programming.

Publication date:
June 2019
Publisher
Packt
Pages
358
ISBN
9781789807332

 

An Introduction to Functional Programming

Why is functional programming useful? Functional programming constructs have popped up in all major programming languages in the past decade. Programmers have enjoyed their benefits—simplified loops, more expressive code, and simple parallelization. But there's more to it—decoupling from time, enabling opportunities to remove duplication, composability, and a simpler design. Higher adoption of functional programming (including the large-scale adoption of Scala in the financial sector) means more opportunities for you once you know and understand it. While we will take a deep dive into functional programming in this book to help you learn, remember that functional programming is another tool to add to your toolbox—one that you can choose to use when the problem and the context fits.

The following topics will be covered in this chapter:

  • An introduction to functional programming and an examination of how you've already been using functional constructs
  • Structured loops versus functional loops
  • Immutability
  • Object-oriented programming (OOP) versus functional design
  • Composability and removing duplication
 

Technical requirements

 

An introduction to functional programming

My first experience with functional programming was at university. I was a 20-year-old geek who was interested in Sci-Fi, reading, and programming; programming was the highlight of my academic life. Everything to do with C++, Java, MATLAB, and a few other programming languages that we used was fun for me. Unfortunately, I can't say the same thing about the disciplines around electrical engineering, circuits, or compiler theory. I just wanted to write code!

Based on my interests, functional programming should have been a very fun course for me. Our teacher was very passionate. We had to write code. But something went wrong—I didn't click with what the teacher was telling us. Why were lists so interesting? Why was the syntax so backward and full of parentheses? Why would I use these things when it was much simpler to write the same code in C++? I ended up trying to translate all the programming constructs I knew from BASIC and C++ into Lisp and OCaml. It completely missed the point of functional programming, but I passed the course and forgot about it for many years.

I imagine that many of you can relate to this story, and I have a possible reason for this. I now believe that my teacher, despite being extremely passionate, used the wrong approach. Today, I understand that functional programming has a certain elegance at its core, due to its strong relationship with mathematics. But that elegance requires a sense of insightful observation that I didn't have when I was 20, that is, a sense that I was lucky to build on after years of various experiences. It's obvious to me now that learning functional programming shouldn't be related to the ability of the reader to see this elegance.

So, what approach could we use instead? Thinking about the past me, that is, the geek who just wanted to write code, there's only one way to go—look at the common problems in code and explore how functional programming reduces or removes them entirely. Additionally, start from the beginning; you've already seen functional programming, you've already used some of the concepts and constructs, and you might have even found them very useful. Let's examine why.

 

Functional programming constructs are everywhere

Around 10 years after I finished the university functional programming course, I had a casual chat with my friend, Felix. As any two geeks, we would rarely see each other, but we had, for years, an ongoing conversation on instant messaging discussing all kinds of nerdy topics and, of course, programming.

Somehow, the topic of functional programming came up. Felix pointed out that one of my favorite and most enjoyable programming languages, LOGO, was, in fact, a functional programming language.

LOGO is an educational programming language whose main characteristic is utilization of so-called turtle graphics.

It was obvious in retrospect; here is how to write a function that draws a square in the KTurtle version of LOGO:

learn square {
repeat 4 {forward 50 turnright 90}
}

The result is shown in the following screenshot:

Can you see how we're passing two lines of code to the repeat function? That's functional programming! A fundamental tenet of functional programming is that code is just another type of data, which can be packed in a function and passed around to other functions. I used this construct in LOGO hundreds of times without making the connection.

This realization made me think: could there be other functional programming constructs that I've used without knowing? As it turns out, yes, there were. In fact, as a C++ programmer, you've most likely used them as well; let's take a look at a few examples:

int add(const int base, const int exponent){
return pow(base, exponent);
}

This function is a typical example of recommended C++ code. I first learned about the benefits of adding const everywhere from the amazing books of Bertrand Meyer: Effective C++, More Effective C++, and Effective STL. There are multiple reasons this construct works well. First, it protects the data members and parameters that shouldn't change. Second, it allows a programmer to reason more easily about what happens in the function by removing possible side effects. Third, it allows the compiler to optimize the function.

As it turns out, this is also an example of immutability in action. As we'll discover in the following chapters, functional programming places immutability at the core of the programs, moving all side effects to the edges of the program. We already know the basic construct of functional programming; to say that we use functional programming just means to use it much more extensively!

Here's another example from STL:

std::vector aCollection{5, 4, 3, 2, 1};
sort (aCollection.begin(), aCollection.end());

The STL algorithms have great power; this power comes from polymorphism. I'm using this term in a more fundamental sense than in OOP—it merely means that it doesn't matter what the collection contains, because the algorithm will still work fine as long as a comparison is implemented. I have to admit that when I first understood it, I was impressed by the smart, effective solution.

There's a variant of the sort function that allows the sorting of elements even when the comparison is not implemented, or when it doesn't work as we'd like; for example, when we are given a Name structure, as follows:

using namespace std;

// Parts of code omitted for clarity
struct Name{
string firstName;
string lastName;
};

If we'd like to sort a vector<Name> container by first name, we just need a compare function:

bool compareByFirstName(const Name& first, const Name& second){
return first.firstName < second.firstName;
}

Additionally, we need to pass it to the sort function, as shown in the following code:

int main(){
vector<Name> names = {Name("John", "Smith"), Name("Alex",
"Bolboaca")};

sort(names.begin(), names.end(), compareByFirstName);
}
// The names vector now contains "Alex Bolboaca", "John Smith"

This makes a kind of higher-order function. A high-level function is a function that uses other functions as parameters in order to allow higher levels of polymorphism. Congratulations—you've just used a second functional programming construct!

I will go as far as to state that STL is a good example of functional programming in action. Once you learn more about functional programming constructs, you'll realize that they are used everywhere in STL. Some of them, such as function pointers or functors, have been in the C++ language for a very long time. In fact, STL has stood the test of time, so why not use similar paradigms in our code as well?

There's no better example to support this statement other than the functional loops present in STL.

 

Structured loops versus functional loops

It's hardly a surprise that one of the first things that we learn as programmers is how to write a loop. One of my first loops in C++ was printing the numbers from 1 to 10:

for(int i = 0; i< 10; ++i){
cout << i << endl;
}

As a curious programmer, I took this syntax for granted, went over its peculiarities and complications, and just used it. Looking back, I realize that there are a few unusual things about this construct. First, why start with 0? I've been told it's a convention, due to historical reasons. Then, the for loop has three statements—an initialization, a condition, and an increment. This sounds slightly too complicated for what we're trying to achieve. Finally, the end condition forced me into more off-by-one errors than I'd like to admit.

At this point, you will realize that STL allows you to use iterators when looping over collections:

for (list<int>::iterator it = aList.begin(); it != aList.end(); ++it)
cout << *it << endl;

This is definitely better than the for loop using a cursor. It avoids off-by-one errors and there are no 0 convention shenanigans. There's still a lot of ceremony around the operation, however. Even worse is that the loop tends to grow as the complexity of the program grows.

There's an easy way to show this symptom. Let's take a look back at the first problems that I've solved using loops.

Let's consider a vector of integers and compute their sum; the naive implementation will be as follows:

int sumWithUsualLoop(const vector<int>& numbers){
int sum = 0;
for(auto iterator = numbers.begin(); iterator < numbers.end();
++iterator){
sum += *iterator;
}
return sum;
}

If only production code was so simple! Instead, the moment we implement this code, we'll get a new requirement. We now need to sum only the even numbers from the vector. Hmm, that's easy enough, right? Let's take a look at the following code:

int sumOfEvenNumbersWithUsualLoop(const vector<int>& numbers){
int sum = 0;
for(auto iterator = numbers.begin(); iterator<numbers.end();
++iterator){
int number = *iterator;
if (number % 2 == 0) sum+= number;
}
return sum;
}

If you thought this is the end, it's not. We now require three sums for the same vector—one of the even numbers, one of the odd numbers, and one of the total. Let's now add some more code, as follows:

struct Sums{
Sums(): evenSum(0), oddSum(0), total(0){}
int evenSum;
int oddSum;
int total;
};

const Sums sums(const vector<int>& numbers){
Sums theTotals;
for(auto iterator = numbers.begin(); iterator<numbers.end();
++iterator){
int number = *iterator;
if(number % 2 == 0) theTotals.evenSum += number;
if(number %2 != 0) theTotals.oddSum += number;
theTotals.total += number;
}
return theTotals;
}

Our loop, which initially started relatively simple, has become more and more complex. When I first started professional programming, we used to blame users and clients who couldn't make up their minds about the perfect feature and give us the final, frozen requirements. That's rarely possible in reality, however; our customers learn new things every day from the interaction of users with the programs we write. It's up to us to make this code clear, and it's possible with functional loops.

Years later, I learned Groovy. A Java virtual machine-based programming language, Groovy focuses on making the job of programmers easier by helping them to write less code and avoid common errors. Here's how you could write the previous code in Groovy:

def isEven(value){return value %2 == 0}
def isOdd(value){return value %2 == 1}
def sums(numbers){
return [
evenSum: numbers.filter(isEven).sum(),
oddSum: numbers.filter(isOdd).sum(),
total: numbers.sum()
]
}

Let's compare the two for a moment. There's no loop. The code is extremely clear. There's no way to make off-by-one errors. There's no counter, so, therefore, there is no starting from 0 weirdness. Additionally, there's no scaffolding around it—I just write what I want to achieve, and a trained reader can easily understand it.

While the C++ version is more verbose, it allows us to achieve the same goals:

const Sums sumsWithFunctionalLoops(const vector<int>& numbers){
Sums theTotals;
vector<int> evenNumbers;
copy_if(numbers.begin(), numbers.end(),
back_inserter(evenNumbers), isEven);
theTotals.evenSum = accumulate(evenNumbers.begin(),
evenNumbers.end(), 0);

vector<int> oddNumbers;
copy_if(numbers.begin(), numbers.end(), back_inserter(oddNumbers),
isOdd);
theTotals.oddSum= accumulate(oddNumbers.begin(), oddNumbers.end(),
0);

theTotals.total = accumulate(numbers.begin(), numbers.end(), 0);

return theTotals;
}

There's still a lot of ceremony though, and too much code similarity. So, let's get rid of it, as follows:

template<class UnaryPredicate>
const vector<int> filter(const vector<int>& input, UnaryPredicate filterFunction){
vector<int> filtered;
copy_if(input.begin(), input.end(), back_inserter(filtered),
filterFunction);
return filtered;
}

const int sum(const vector<int>& input){
return accumulate(input.begin(), input.end(), 0);
}

const Sums sumsWithFunctionalLoopsSimplified(const vector<int>& numbers){
Sums theTotals(
sum(filter(numbers, isEven)),
sum(filter(numbers, isOdd)),
sum(numbers)
);
return theTotals;
}

We've just replaced a complex for loop with a number of simpler, more readable, and composable functions.

So, is this code better? Well, that depends on your definition of better. I like to think of any implementation in terms of advantages and disadvantages. The advantages of functional loops are simplicity, readability, reduced code duplication, and composability. Are there any disadvantages? Well, our initial for loop only requires one pass through the vector, while our current implementation requires three passes. This can be a burden for very large collections, or when response time and memory usage are very important. This is definitely worth discussing, and we will examine it in more detail in Chapter 10Performance Optimization, which is focused solely on performance optimization for functional programming. For now, I recommend that you focus on understanding the new tool of functional programming.

In order to do that, we need to revisit immutability.

 

Immutability

We've already understood that a certain level of immutability is preferred in C++; the common example is as follows:

class ...{
int add(const int& first, const int& second) const{
return first + second;
}
}

The const keyword clearly communicates a few important constraints on the code, such as the following:

  • The function does not change any of its arguments before returning.
  • The function does not change any data member of the class it belongs to.

Let's now imagine an alternate version of add, as follows

int uglyAdd(int& first, int& second){
first = first + second;
aMember = 40;
return first;
}

I called this uglyAdd for a reason—I don't tolerate code like this when I'm programming! This function violates the principle of minimal surprise and does too many things. Reading the function code reveals nothing about its intent. Imagine the surprise of the caller, if not careful, then, just by calling an add function, two things changed—one in the parameters passed, and the second in the class where the function is located.

While this is an extreme example, it contributes to an argument for immutability. Immutable functions are boring; they receive data, change nothing in the received data, change nothing in the class containing them, and return a value. When it comes to maintaining code over long periods of time, however, boring is good.

Immutability is the core property of functions in functional programming. Of course, there's at least one part of your program that cannot be immutable—input/output (I/O). We will accept I/O for what it is, and we will focus on increasing the immutability of our code as much as possible.

Now, you are probably wondering whether you have to completely rethink the way you write programs. Should you forget all that you learned about OOP? Well, not really, and let's see why.

 

OOP versus functional design styles

An important part of my job is to work with programmers and help them to improve the way they write code. To do so, I try my best to come up with simple explanations for complex ideas. I have one such explanation for software design. Software design is, for me, the way we structure the code such that we optimize it for business purposes.

I like this definition because it's plain and short. But one thing bugged me after I started experimenting with functional constructs; that is, functional programming leads to code such as the following:

const Sums sumsWithFunctionalLoopsSimplified(const vector<int>& numbers){
Sums theTotals(
sum(filter(numbers, isEven)),
sum(filter(numbers, isOdd)),
sum(numbers)
);
return theTotals;
}

Writing similar code in OOP style would most likely mean creating classes and using inheritance. So, which style is better? Additionally, if software design is about code structure, is there an equivalence between the two styles?

First, let's take a look at what the two design styles really promote. What is OOP? For many years, I believed all the books that listed the following three properties of object-oriented languages:

  • Encapsulation
  • Inheritance
  • Polymorphism

Alan Kay, the thinker behind OOP, does not really agree with this list. For him, OOP is about communication between many small objects. As a biology major, he saw an opportunity to organize programs like the body organizes cells, and to allow objects to communicate much like cells do. He places more importance on objects over classes, and on communication over the commonly listed OOP properties. I would best summarize his position as follows: the dynamic relations in the system are more important than its static properties.

This changes a lot about the OOP paradigm. So, should classes match the real world? Not really. They should be optimized for the representation of the real world. Should we focus on having clear, well-thought out class hierarchies? No, since those are less important than the communication between objects. What is the smallest object that we can think of? Well, either a combination of data, or a function.

In a recent answer on Quora (https://www.quora.com/Isnt-getting-rid-of-the-evil-state-like-Haskells-approach-something-every-programmer-should-follow/answer/Alan-Kay-11), Alan Kay stated an interesting idea when answering a question on functional programming. Functional programming came from mathematics and from an effort to model the real world in order to enable artificial intelligence. This effort hit the following problem—Alex is in Bucharest and Alex is in London can both be true, but at different points in time. The solution to this modeling issue is immutability; that is, time becomes a parameter to functions, or a data member in the data structures. In any program, we can model data changes as time-bound versions of the data. Nothing stops us from modeling the data as small objects, and the changes as functions. Additionally, as we will see later, we can easily turn functions into objects and vice versa.

So, to summarize, there's no real tension between OOP as Alan Kay meant it and functional programming. We can use them together and interchangeably, as long as we focus on increasing the immutability of our code, and on small objects that communicate with one another. We'll discover, in the following chapters, how easy it is to replace a class with functions and vice versa.

But there are many ways to use OOP that are different from Alan Kay's vision. I've seen a lot of C++ code with my clients, and I've seen it all—big functions, huge classes, and deep inheritance hierarchies. Most of the time, the reason I'm called is because the design is too hard to change and because adding new features slows down to a crawl. Inheritance is a very strong relationship and overusing it leads to strong coupling, and, therefore, to code that's difficult to change. Long methods and long classes are harder to understand and harder to change. Of course, there are situations when inheritance and long classes make sense, but, in general, going for small objects with loose coupling enables changeability.

But classes can be reused, can't they? Can we do that with functions? Let's visit this topic next.

 

Composability and removing duplication

We have already seen an example of where we had a fair amount of duplication:

const Sums sumsWithFunctionalLoops(const vector<int>& numbers){
Sums theTotals;
vector<int> evenNumbers;
copy_if(numbers.begin(), numbers.end(), back_inserter(evenNumbers),
isEven);
theTotals.evenSum = accumulate(evenNumbers.begin(),
evenNumbers.end(), 0);

vector<int> oddNumbers;
copy_if(numbers.begin(), numbers.end(), back_inserter(oddNumbers),
isOdd);
theTotals.oddSum= accumulate(oddNumbers.begin(), oddNumbers.end(),
0);

theTotals.total = accumulate(numbers.begin(), numbers.end(), 0);

return theTotals;
}

We managed to reduce it using functions, as shown in the following code:

template<class UnaryPredicate>
const vector<int> filter(const vector<int>& input, UnaryPredicate filterFunction){
vector<int> filtered;
copy_if(input.begin(), input.end(), back_inserter(filtered),
filterFunction);
return filtered;
}

const int sum(const vector<int>& input){
return accumulate(input.begin(), input.end(), 0);
}

const Sums sumsWithFunctionalLoopsSimplified(const vector<int>& numbers){
Sums theTotals(
sum(filter(numbers, isEven)),
sum(filter(numbers, isOdd)),
sum(numbers)
);

return theTotals;
}

It's interesting to see how the functions are composed in various ways; we have sum(filter()) called twice, and sum() called once. Moreover, filter can be used with multiple predicates. Additionally, with a bit of work, we can make both filter and sum polymorphic functions:

template<class CollectionType, class UnaryPredicate>
const CollectionType filter(const CollectionType& input, UnaryPredicate filterFunction){
CollectionType filtered;
copy_if(input.begin(), input.end(), back_inserter(filtered),
filterFunction);
return filtered;
}
template<typename T, template<class> class CollectionType>
const T sum(const CollectionType<T>& input, const T& init = 0){
return accumulate(input.begin(), input.end(), init);
}

It's now easy to call filter and sum with arguments of type other than vector<int>. The implementation is not perfect, but it illustrates the point that I'm trying to make, that is, small, immutable functions can easy become polymorphic and composable. This works especially well when we can pass functions to other functions.

 

Summary

We've already covered a lot of interesting topics! You've just realized that you know the basics of functional programming. You can write immutable functions in C++ with the help of the const keyword. You've already used high-level functions from STL. Additionally, you don't have to forget anything about OOP, but, instead, just see it from a different perspective. Finally, we discovered how small immutable functions can be composed to offer complex functionality, and how they can become polymorphic with the help of C++ templates.

It's now time to take an in-depth look at the building blocks of functional programming and learn how to use them in C++. This includes pure functions, lambdas, and operations with functions such as functional composition, currying, or partial functional application.

 

Questions

  1. What is an immutable function?
  2. How do you write an immutable function?
  3. How do immutable functions support code simplicity?
  4. How do immutable functions support simple design?
  5. What is a high-level function?
  6. What example of high-level function can you give from STL?
  7. What are the advantages of functional loops over structured loops? What are the potential disadvantages?
  8. What is OOP from the perspective of Alan Kay? How does it relate to functional programming?

About the Author

  • Alexandru Bolboaca

    With 20 years' experience in the software development industry, Alexandru Bolboaca has gone from being a junior C++ programmer to a technical lead and software architect, before becoming a technical coach and trainer. He has extensive experience in helping customers to improve the way they work, as well as their code and approach to testing. He is also the author of Usable Software Design, and the co-author of Coderetreat.

    Browse publications by this author

Latest Reviews

(1 reviews total)
2/3 read yet, a good book, especially for the price. I think I'm just too advance in C++ to really appreciate the full value of this book.

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial