1 Learning functional programming

published book

In this chapter you will learn

  • who this book is for
  • what a function is
  • how useful functional programming is
  • how to install needed tools
  • how to use this book

I can only approach you by particular examples and let you infer what it is.

—Richard Hamming, “Learning to Learn”

Perhaps you picked up this book because ...

You are curious about functional programming 1

You heard about functional programming, read the Wikipedia entry, and looked at a few books too. Maybe you rolled your eyes at the mathy explanations behind all the code, but you still remained curious about it.

I dreamed about writing the least intimidating functional programming book ever. This is it: entry-level, practical, and with as little eye-rolling as possible.

You tried to learn functional programming before 2

You have tried learning functional programming more than once and still don’t get it. Just when you understand one key concept, the next obstacle waits around the corner. And this obstacle requires understanding many more things before even approaching it.

Learning functional programming should be enjoyable. This book encourages you to take small steps and assumes that your endorphins will keep you going.

You are still on the fence 3

You have been programming for many years in an object-oriented or imperative programming language. You have experienced the buzz of functional programming, read some blog posts, and tried coding a bit. Still, you cannot see how it makes your programming life better.

This book is heavily focused on practical applications of functional programming. It will add some functional concepts to your mental toolbox. You will be able to use them—no matter what language you use.

Or maybe something else?

Whatever your reasons, this book tries to address them differently. It focuses on learning through experimentation and play. It encourages you to ask questions and come up with answers by coding. It will help you grow to new levels as a programmer. I hope you’ll enjoy the ride.

What do you need to know before we start?

We assume that you have been developing software in any of the popular languages, such as Java, C++, C#, JavaScript, or Python. This is a very vague statement, so here are some quick checklists that will help us make sure we are on the same page.

You will follow along comfortably if

  • You are familiar with basic object-orientation concepts like classes and objects.
  • You are able to read and comprehend code like this:
class Book {
     private String title;                  #1
     private List<Author> authors;          #1
     public Book(String title) {            #2
       this.title = title;
       this.authors = new ArrayList<Author>();
     }
  
     public void addAuthor(Author author) { #3
       this.authors.add(author);
     }
   }

You will get maximum benefits if

  • You have had problems with stability, testability, regression or integration of your software modules.
  • You have experienced problems debugging code like this:
public void makeSoup(List<String> ingredients)  { #1
     if(ingredients.contains("water")) {
       add("water");
     } else throw new NotEnoughIngredientsException();
     heatUpUntilBoiling();
     addVegetablesUsing(ingredients);
     waitMinutes(20);
   }

You don’t need to

  • Be an expert in object orientation
  • Be a Java/C++/C#/Python master
  • Know anything about any functional programming language, such as Kotlin, Scala, F#, Rust, Clojure, or Haskell

What do functions look like?

Without further ado, let’s jump right into some code! We don’t really have all the necessary tools set up yet, but this won’t stop us, will it?

Here are a bunch of different functions. All of them have something in common: they get some values as inputs, do something, and maybe return values as outputs. Let’s see:

public static int add(int a, int b) {              #1
  return a + b;
}

public static char getFirstCharacter(String s) {   #2
  return s.charAt(0);
}

public static int divide(int a, int b) {           #3
  return a / b;
}

public static void eatSoup(Soup soup) {
  // TODO: "eating the soup" algorithm             #4
}

Why all the public statics?

You probably wonder about the public static modifier in each definition. Well, it’s there on purpose. Functions we use in this book are all static (i.e., they don’t need any object instance to be executed). They are free—they can be called by anybody from anywhere, as long as the caller has the input parameters they require. They work only with the data the caller provides—nothing more.

This has, of course, some major ramifications, which we will address later in the book. For now, let’s remember that when we say function, we mean a public static function that can be called from anywhere.

Quick exercise

Implement the two functions below:

public static int increment(int x) {
 // TODO
}
public static String concatenate(String a, String b) {
 // TODO
}

Answers:

return x + 1;
return a + b;

Meet the function

As we’ve seen, functions come in different flavors. Basically, each function consists of a signature and a body, which implements the signature.

public static int add(int a, int b) { #1
  return a + b;                       #2
}

In this book we will focus on functions which return values because, as we shall see, these functions are at the heart of functional programming. We won’t use functions that return nothing (i.e., void).

We can treat a function as a box that gets an input value, does something with it, and returns an output value. Inside the box is the body. Types and names of input and output values are part of the signature. So we can represent the add function as follows:

Signature vs. body

In the diagrams above, the implementation of the function, its body, is hidden inside the box, while the signature is publicly visible. This is a very important distinction. If the signature alone is enough to understand what’s going on inside the box, it is a big win for the programmers who read the code because they don’t need to go inside and analyze how it’s implemented before they use it.

Quick exercise

Draw a function diagram for the function below. What’s inside the box?

public static int increment(int x)

Answer:

There is a single arrow going in, named int x, and a single arrow going out, named int. The implementation is return x + 1;

When the code lies ...

Some of the most difficult problems a programmer encounters happen when a code does something it’s not supposed to do. These problems are often related to the signature telling a different story than the body. To see this in action, let’s briefly revisit the four functions we’ve seen earlier:

public static int add(int a, int b) {
  return a + b;
}

public static char getFirstCharacter(String s) {
  return s.charAt(0);
}

public static int divide(int a, int b) {
  return a / b;
}

public static void eatSoup(Soup soup) {
  // TODO: "eating a soup" algorithm
}

Surprisingly, three of the above four functions lie.

getFirstCharacter promises that when we provide a String, it will give us a char in return. However, sneaky as we are, when we provide an empty String, it will not give us any char, it will throw an exception!

divide will not provide a promised int if we give it 0 as b.

eatSoup promises to eat the soup we provide, but when we do, it does nothing and returns void. This is probably what most children have as the default implementation.

add, on the other hand, will return an int, no matter what we provide as a and b—as promised! We can count on such functions! *

In this book, we will focus on functions that don’t lie. We want their signatures to tell the whole story about the body. You will learn how to build real-world programs using only these kinds of functions.

* After reading this book, rewriting your functions to their trustworthy functional counterparts will be a piece of cake for you!

Imperative vs. declarative

Some programmers divide programming languages into two main paradigms: imperative and declarative. Let’s try to grasp the difference between these two paradigms by going through a simple exercise.

Imagine we are tasked with creating a function that calculates a score in some word-based game. A player submits a word, and the function returns a score. One point is given for each character in the word.

Calculating the score imperatively

public static int calculateScore(String word)  { #1
  int score = 0;
  for(char c : word.toCharArray()) {
    score++;
  }
  return score;
}

Imperative programming focuses on how the result should be computed. It is all about defining specific steps in a specific order. We achieve the final result by providing a detailed step-by-step algorithm.

Calculating the score declaratively

public static int wordScore(String word)  { #1
  return word.length();
}

The declarative approach focuses on what needs to be done—not how. In this case we are saying we need a length of this string, and we return this length as the score for this particular word. That’s why we can just use the length method from Java’s String to get the number of characters, and we don’t care how it was computed.

We also changed the name of the function from calculateScore to wordScore. This may seem like a minor difference, but using a noun makes our brain switch into the declarative mode and focus on what needs to be done rather than the details of how to achieve it.

Declarative code is usually more succinct and more comprehensible than imperative code. Even though many internals, like the JVM or CPUs, are strongly imperative, we, as application developers, can heavily use the declarative approach and hide imperative internals, just like we did with the length function. In this book, you will learn how to write real-world programs using the declarative approach.

By the way, SQL is also a mostly declarative language. You usually state what data you need, and you don’t really care how it’s fetched (at least during development).

Coffee break: Imperative vs. declarative

Welcome to the very first coffee break exercise section of the book! We’ll try to make sure you have grasped the difference between imperative and declarative approaches.

* You learn the most when you struggle at first!

In this exercise, we need to enhance our imperative calculateScore and declarative wordScore functions. The new requirement says that the score of the word should now be equal to the number of characters that are different than 'a'. Here’s your task. You are given the code below:

public static int calculateScore(String word)  {
 int score = 0;
 for(char c : word.toCharArray()) {
   score++;
 }
 return score;
}
public static int wordScore(String word)  {
 return word.length();
}

Change the functions above so that the following are true:

calculateScore("imperative") == 9     wordScore("declarative") == 9
calculateScore("no") == 2             wordScore("yes") == 3 #1

Coffee break explained: Imperative vs. declarative

I hope you’ve enjoyed your first coffee break. Now it’s time to check your answers. Let’s start with the imperative solution.

Imperative solution

The imperative approach strongly encourages us to directly implement the algorithm—the “how.” So we need to get the word, go through all the characters in this word, increment the score for each character that is different than 'a', and return the final score when finished.

public static int calculateScore(String word)  {
  int score = 0;
  for(char c : word.toCharArray()) {
    if(c != 'a')
      score++;
  }
  return score;
}

And that’s it! We just added an if clause inside the for loop.

Declarative solution

The declarative approach focuses on the what. In this case, the requirement was already defined declaratively: “The score of the word should now be equal to the number of characters that are different than 'a'.” We can almost directly implement this requirement as

public static int wordScore(String word)  {
  return word.replace("a", "").length();
}

Alternatively, we can introduce a helper function.

public static String stringWithoutChar(String s, char c) {
 return s.replace(Character.toString(c), "");
}

public static int wordScore(String word)  {
 return stringWithoutChar(word, 'a').length();
}

You may have come up with a different solution. Its is acceptable if it focuses on the string without as (what), instead of fors and ifs (how).

How useful is learning functional programming?

Functional programming is programming using functions with

  • Signatures that don’t lie
  • Bodies that are as declarative as possible

In this book, we’ll dive deeper into these topics, step by step, and eventually we will be able to build real-world programs without even thinking about the old habits. This alone will be a game changer. However, the benefits don’t stop here. There are other useful side effects you acquire when learning functional programming with this book.

It’s a style of writing code in any language

So far, we have used Java to write functions, even though it is considered an object-oriented imperative language. It turns out the techniques and features of declarative and functional programming are making their way into Java and other traditionally imperative languages. You can already use some of the techniques in your language of choice.

Functional concepts are the same in FP languages

This book focuses on general, universal features and techniques of functional programming. This means that if you learn a concept here, using Scala, you will be able to apply it in many other functional programming languages out there. We are focusing on things that are common between many FP languages, not on single-language specifics.

Functional and declarative thinking

One of the most important skills you will learn is the different approach to solving programming problems. By mastering all those functional techniques, you will add another, very powerful, tool to your software engineer toolbox. This new perspective will definitely help you grow in your profession, no matter how your story has unfolded so far.

Leaping into Scala

The majority of examples and exercises in this book use Scala. If you don’t know this language, don’t worry, you will learn all the necessary basics very soon.

* We will still use some Java to present the imperative examples. The intention is to use Scala only for the completely functional code snippets.

Meet the function ... in Scala

Earlier in this chapter, we met our first function, written in Java. The function accepted two integer parameters and returned the sum of them.

public static int add(int a, int b) {
 return a + b;
}

It’s time to rewrite this function in Scala and learn some new syntax.

* Scala allows us to omit braces (they are optional). If a programmer doesn’t include braces, then the compiler assumes that the indentation is significant, like in Python. You can use this feature if you prefer it. However, we will include braces in this book because we want to spend a minimum amount of time focusing on syntax differences, as mentioned.

Practicing functions in Scala

Now that we know the syntax of a function in Scala, we can try to rewrite some of our previous Java snippets in Scala. Hopefully, this will make the transition a little easier.

There are three types of exercises in the book. You’ve already encountered two of them: quick exercise (small exercises marked with a big question mark and easy to solve in your head) and coffee breaks (longer and harder and aimed to make you think about a concept from a different perspective, using a piece of paper or a computer).

The third type is a practicing ... exercise. This is the most tedious of the three because it’s heavily based on repetition. Usually, you are tasked with three to five exercises that are solved in exactly the same way. This is done on purpose—to train your muscle memory. The things you learn in those sections are used extensively in the book, so it’s important to get them into your system as quickly as possible.

Your task is to rewrite the three functions below from Java to Scala: *

public static int increment(int x) {
 return x + 1;
}

public static char getFirstCharacter(String s) {
 return s.charAt(0);
}

public static int wordScore(String word)  {
 return word.length();
}

* We haven’t talked about the tools we need to install on our computers to write Scala, so please do this one on a piece of paper.

Notes:
- String in Scala has exactly the same API as String in Java.
- Character type in Scala is Char.
- Integer type in Scala is Int.
- We don’t need semicolons in Scala.

Answers

def increment(x: Int): Int = {
 x + 1
}

def getFirstCharacter(s: String): Char = {
 s.charAt(0)
}

def wordScore(word: String): Int = {
 word.length()
}

Getting your tools ready

It’s time to start writing some functional Scala code on the actual computer. To do that, we need to install some things. As each computer system is different, please follow these steps with caution.

Download the book’s companion source code project

1

First and foremost: each piece of code that you see in this book is also available in the book’s companion Java/Scala project. Download or check it out by going to https://michalplachta.com/book. The project comes with a README file that has all the up-to-date details on how to get started. *

* If you fancy a more automatic way of installing the JDK/Scala or you prefer to use Docker or a web interface, make sure to visit the book’s website to learn about alternative ways of coding exercises in the book.

Install the Java Development Kit (JDK)

Let’s make sure you have the JDK installed on your computer. This will allow us to run Java and Scala (which is a JVM language) code. If you are unsure, please run javac -version in your terminal, and you should get something like javac 17. If you don’t, please visit https://jdk.java.net/17/. *

* JDK 17 is the latest long-term-support (LTS) version as of writing this book. Other LTS versions should be fine too.

Install sbt (Scala build tool)

sbt is a build tool used in the Scala ecosystem. It can be used to create, build, and test projects. Visit https://www.scala-sbt.org/download.html to get the instructions of how to install the sbt on your platform.

Note: We recommend using the REPL (sbt console) with this book, especially at the beginning, because it works out of the box, and you won’t get distracted. You will be able to load all exercises directly into your REPL. However, when you get familiar with the way exercises work, you are free to switch to an IDE. The most beginner-friendly one is IntelliJ IDEA. After installing Java, you can download this IDE from https://www.jetbrains.com/idea/.

Run it!

In your shell, you will need to run an sbt console command, which will start the Scala read–evaluate–print loop (REPL). This is the preferred tool to run examples and do exercises in this book. It allows you to write a line of code, press Enter, and get feedback immediately. If you run this command inside the book’s source code folder, you will additionally get access to all exercises, which should come in handy, especially later in the book, when they get more complicated. Let’s not get ahead of ourselves, though, and start playing around with the REPL itself. Have a look below to get some intuition on how to use this tool. After running sbt console:

Welcome to Scala 3.1.3             #1
Type in expressions for evaluation. Or try :help.

scala>                             #2
scala> 20 + 19

val res0: Int = 39                 #3

Getting to know the REPL

Let’s do a quick REPL session together and learn some new Scala tricks along the way!

scala> print("Hello, World!")                        #1
Hello, World!                                        #2

scala> val n = 20                                    #3
val n: Int = 20                                      #4

scala> n * 2 + 2                                     #5
val res1: Int = 42                                   #6

scala> res1 / 2                                      #7
val res2: Int = 21                                   #8

scala> n                                             #9
val res3: Int = 20

scala> :load src/main/scala/ch01_IntroScala.scala    #10
def increment(x: Int): Int
def getFirstCharacter(s: String): Char
def wordScore(word: String): Int
// defined object ch01_IntroScala

scala> :quit                                         #11

Writing your first functions!

The time has come! This is the moment when you write (and use!) your first functions in Scala. We’ll use the ones we are already familiar with.

Fire up the Scala REPL (sbt console), and write

scala> def increment(x: Int): Int = {
     |   x + 1 #1
     | }       #1
def increment(x: Int): Int

As you can see, the REPL responded with a line of its own. It said that it understood what we typed: the name is increment, and it is a function that gets a parameter x of type Int and returns an Int back. Let’s use it!

scala> increment(6)
val res0: Int = 7

We called our function by applying 6 as the argument. The function returned 7, as expected! Additionally, the REPL named this value res0.

>

We will use this graphic to indicate that you should try to write code in your own REPL session.

Now, let’s try to write and call another function we met earlier:

>

def wordScore(word: String): Int = {
  word.length()
}

wordScore("Scala")
 5

>



scala> wordScore("Scala") 
val res1: Int = 5

Again, this is how the snippet on the above may look in your REPL.

How to use this book

Before we wrap up this chapter, let’s first go through all the ways this book can and should be used. Remember, this is a technical book, so do not expect to read it from cover to cover in one sitting. Instead, keep the book at your desk, working with it next to your keyboard and a few sheets of paper to write on. Shift your perspective from being a receiver of thoughts to being an active participant. Following are some additional tips.

Do the exercises

Make the commitment to do every exercise. Resist the temptation to cut and paste code or absentmindedly transfer it from the book into the REPL. *

* Don’t look up answers, especially for coffee breaks. It may feel good to solve an exercise very quickly, but it impedes your long-term learning success.

Quick exercises, coffee breaks, and practicing ... sections

There are three types of exercises in the book:

  
  • Quick exercises are small exercises that can be completed without any external tools—in your head.

  • Coffee breaks are longer and harder and aimed to make you think about a concept from a different perspective. This usually requires you to use a piece of paper or a computer.

  • Practicing ... sections are based heavily on repetition. They are used to train your muscle memory in concepts and techniques that are critical in the rest of the book.

Create a space to learn

Keep some paper nearby and a few pencils or pens of different colors. Sharpies or flip chart markers are good too. We want you to work in a space that radiates information—not a dull, sterile place.

Don’t rush

Work at a pace that is comfortable. It’s OK to not have a consistently steady pace, too. Sometimes we run; other times we crawl. Sometimes we do nothing. Rest is very important. Remember that some topics may be harder than others. *

* On the other hand, if it feels easy, you’re not learning.

Write code and lots of it

This book has hundreds of snippets that you can transfer directly into your REPL session. Each chapter is written as a “REPL story,” but you are encouraged to play with the code, write your own versions, and just have fun! *

* Remember that all the code you encounter in this book is available in the book’s companion source code repository.

Summary

In this chapter you learned five very important skills and concepts, which we’ll use as a foundation in the rest of the book.

Who is this book for?

We started by defining you—the reader. There are three main reasons you may have chosen this book: maybe you are just curious about FP or you didn’t have enough time or luck to learn it properly before, or maybe you learned it before and didn’t like it very much. Whatever the reasons, our reader is a programmer who wants to learn some new ways of creating real-world applications and do so by experimentation and play. We require that you are acquainted with an object-oriented language like Java.

What is a function?

Then, we introduced our protagonist, the function, and talked a little bit about signatures and bodies. We also touched on a problem that we encounter when signatures are not telling the whole story about the body and how it makes programming such a difficult endeavor.

How useful is functional programming?

We talked about the difference between imperative and declarative programming, defining, roughly, what functional programming is and how it can help you grow as a software professional.

Installing needed tools

We installed sbt and used the Scala REPL to write our first functions in Scala. We learned how the code snippets from the book work in the REPL and how we use to denote REPL responses in code snippets.

>

REPL sessions are marked with this graphic throughout the book. Remember to :reset your session before starting a new chapter.

Learning how to use this book

Finally, we went through all the administrative features of the book. We described three types of exercises (quick exercises, coffee breaks, and practicing ... sections), discussed how to prepare your workspace to learn as much as possible, and described how to work with the code snippets. You can copy and paste them into your REPL session, transfer them manually, or :load them from the Scala files included in the book’s repository. Remember to get the source code by going to https://michalplachta.com/book. There is also a README file there that will help you set everything up.

Get Grokking Functional Programming
buy ebook for  $47.99 $33.59
sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage
Up next...
  • why we need pure functions
  • how to pass copies of the data
  • how to recalculate instead of storing
  • how to pass the state
  • how to test pure functions