This chapter covers: (Functional Programming in Java)
Not everybody agrees on a definition for functional programming (FP). In general terms, functional programming is a programming paradigm, and it’s about programming with functions. But this doesn’t explain the most important aspect: how FP is different from other paradigms, and what makes it a (potentially) better way to write programs. In his article “Why Functional Programming Matters,” published in 1990, John Hughes writes the following:
John Hughes, “Why Functional Programming Matters,” from D. Turner, ed., Research Topics in Functional Programming (Addison-Wesley, 1990), 17–42, www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf.
In the rest of this chapter, I’ll briefly present concepts such as referential transparency and the substitution model, as well as other concepts that together are the essence of functional programming. You’ll apply these concepts over and over in the coming chapters.
What is functional programming?
It’s often as important to understand what something is not, as to agree about what it is. If functional programming is a programming paradigm, there clearly must be other programming paradigms that FP differs from. Contrary to what some might think, functional programming isn’t the opposite of object-oriented programming (OOP). Some functional programming languages are object-oriented; some are not. Functional programming is sometimes considered to be a set of techniques that supplement or replace techniques found in other programming paradigms, such as:
Although it is true that most functional languages do use a number of these techniques, you may find, for each of them, examples of functional programming languages that don’t, as well as non-functional languages that do. As you’ll see when studying each of these techniques in this book, it’s not the language that makes programming functional. It’s the way you write the code. But some languages are more functional-friendly than others.
What functional programming may be opposed to is the imperative programming paradigm. In imperative programming style, programs are composed from elements that “do” something. “Doing” something generally implies an initial state, a transition, and an end state. This is sometimes called state mutation. Traditional imperative-style programs are often described as a series of mutations, separated with condition testing. For example, an addition program for adding two positive values a and b might be represented by the following pseudo code:
In this pseudo code, you can recognize the traditional instructions of most imperative languages: testing conditions, mutating variables, branching, and returning a value. This code may be represented graphically by a flow chart, such as figure 1.1.
Figure 1.1. A flow chart representing an imperative program as a process that occurs in time. Various things are transformed and states are mutated until the result is obtained.
On the other hand, functional programs are composed of elements that “are” something—they don’t “do” something. The addition of a and b doesn’t “make” a result. The addition of 2 and 3, for example, doesn’t make 5. It is 5. The difference might not seem important, but it is. The main consequence is that each time you encounter 2 + 3, you can replace it with 5. Can you do the same thing in an imperative program? Well, sometimes you can. But sometimes you can’t without changing the program’s outcome. If the expression you want to replace has no other effect than returning the result, you can safely replace it with its result. But how can you be sure that it has no other effect? In the addition example, you clearly see that the two variables a and b have been destroyed by the program. This is an effect of the program, besides returning the result, so it’s called a side effect. (This would be different if the computation were occurring inside a Java method, because the variables a and b would be passed by value, and the change would then be local and not visible from outside the method.)
One major difference between imperative programming and FP is that in FP there are no side effects. This means, among other things,
When I say “no side effects,” I mean no observable side effects. Functional programs are built by composing functions that take an argument and return a value, and that’s it. You don’t care about what’s happening inside the functions, because, in theory, nothing is happening ever. But in practice, programs are written for computers that aren’t functional at all. All computers are based on the same imperative paradigm; so functions are black boxes that
This is theory. In practice, it’s impossible for a function to have no side effects at all. A function will return a value at some time, and this time may vary. This is a side effect. It might create an out-of-memory error, or a stack-overflow error, and crash the application, which is a somewhat observable side effect. And it will cause writing to memory, registering mutations, thread launching, context switching, and other sorts of things that are indeed effects observable from outside.
So functional programming is writing programs with no intentional side effects, by which I mean side effects that are part of the expected outcome of the program. There should also be as few non-intentional side effects as possible.
Writing useful programs with no side effects
You may wonder how you can possibly write useful programs if they have no side effects. Obviously, you can’t. Functional programming is not about writing programs that have no observable results. It’s about writing programs that have no observable results other than returning a value. But if this is all the program does, it won’t be very useful. In the end, functional programs have to have an observable effect, such as displaying the result on a screen, writing it to a file or database, or sending it over a network. This interaction with the outside world won’t occur in the middle of a computation, but only when you finish the computation. In other words, side effects will be delayed and applied separately.
Take the example of the addition in figure 1.1. Although it’s described in imperative style, it might yet be functional, depending on how it’s implemented. Imagine this program is implemented in Java as follows:
How referential transparency makes programs safer
Having no side effects (and thus not mutating anything in the external world) isn’t enough for a program to be functional. Functional programs must also not be affected by the external world. In other words, the output of a functional program must depend only on its argument. This means functional code may not read data from the console, a file, a remote URL, a database, or even from the system. Code that doesn’t mutate or depend on the external world is said to be referentially transparent. Referentially transparent code has several properties that might be of some interest to programmers:
Figure 1.2 illustrates the difference between a referentially transparent program and one that’s not referentially transparent.
The benefits of functional programming
From what I’ve just said, you can likely guess the many benefits of functional programming:
Functional programs are inherently thread-safe because they avoid mutation of shared state. Once again, this doesn’t mean that all data has to be immutable. Only shared data must be. But functional programmers will soon realize that immutable data is always safer, even if the mutation is not visible externally.
Using the substitution model to reason about programs
Remember that a function doesn’t do anything. It only has a value, which is only dependent on its argument. As a consequence, it’s always possible to replace a function call, or any referentially transparent expression, with its value, as shown in figure 1.3.
Figure 1.3. Replacing referentially transparent expressions with their values doesn’t change the overall meaning.
When applied to functions, the substitution model allows you to replace any function call with its return value. Consider the following code:
Applying functional principles to a simple example
As an example of converting an imperative program into a functional one, we’ll consider a very simple program representing the purchase of a donut with a credit card.
In this code, the charging of the credit card is a side effect (1). Charging a credit card probably consists of calling the bank, verifying that the credit card is valid and authorized, and registering the transaction. The function returns the donut (2). The problem with this kind of code is that it’s difficult to test. Running the program for testing would involve contacting the bank and registering the transaction using some sort of mock account. Or you’d need to create a mock credit card to register the effect of calling the charge method and to verify the state of the mock after the test.
If you want to be able to test your program without contacting the bank or using a mock, you should remove the side effect. Because you still want to charge the credit card, the only solution is to add a representation of this operation to the return value. Your buyDonut method will have to return both the donut and this representation of the payment. To represent the payment, you can use a Payment class.
- Listing 1.2. The Payment class
- Listing 1.3. The Tuple class
The combine method in the following listing allows you to combine payments. Note that if the credit cards don’t match, an exception is thrown. This doesn’t contradict what I said about functional programs not throwing exceptions. Here, trying to combine two payments with two different credit cards is considered a bug, so it should crash the application. (This isn’t very realistic. You’ll have to wait until chapter 7 to learn how to deal with such situations without throwing exceptions.)
- Listing 1.4. Composing multiple payments into a single one
- , Payment>.
- Listing 1.5. Buying multiple donuts at once
Now, your program can be tested without using a mock. For example, here’s a test for the method buyDonuts:
- groupBy Ex1:
Now let's take a look at another useful function used frequently in FP:
This method of List uses an operation to reduce the list to a single value. This operation is represented by Function> f. This notation may look a bit weird, but you’ll learn what it means in chapter 2. It could be, for example, an addition. In such a case, it would simply mean a function such as f(a, b) = a + b. A simple usage example:
Using these methods, you can now create a new method that groups payments by credit card.
Note that you could use a method reference in the last line of the groupByCard method, but I chose the lambda notation because it’s probably (much) easier to read. If you prefer method references, you can replace this line with the following one:
In listing 1.6, the portion after c1 -> is a function taking a single parameter and passing that parameter to c1.combine(). And that’s exactly what c1::combine is—it’s a function taking a single parameter. Method references are often easier to read than lambdas, but not always!
Pushing abstraction to the limit
As you’ve seen, functional programming consists in writing programs by composing pure functions, which means functions without side effects. These functions may be represented by methods, or they may be first-class functions, such as the arguments of methods groupBy, map, or reduce, in the previous example. First-class functions are simply functions represented in such a way that, unlike methods, they can be manipulated by the program. In most cases, they’re used as arguments to other functions, or to methods. You’ll learn in chapter 2 how this is done.
But the most important notion here is abstraction. Look at the reduce method. It takes as its argument an operation, and uses it to reduce a list to a single value. Here, the operation has two operands of the same type. Except for this, it could be any operation. Consider a list of integers. You could write a sum method to compute the sum of the elements; you could write a product method to compute the product of the elements; or you could write a min or a max method to compute the minimum or the maximum of the list. But you could also use the reduce method for all these computations. This is abstraction. You abstract the part that is common to all operations in the reduce method, and you pass the variable part (the operation) as an argument.
But you could go further. The reduce method is a particular case of a more general method that might produce a result of a different type than the elements of the list. For example, it could be applied to a list of characters to produce a String. You’d need to start from a given value (probably an empty string). In chapters 3 and 5, you’ll learn how to develop this method (called fold). Also note that the reduce method won’t work on an empty list. Think of a list of integers—if you want to compute the sum, you need to have an element to start with. If the list is empty, what should you return? Of course, you know that the result should be 0, but this only works for a sum. It doesn’t work for a product.
Also consider the groupByCard method. It looks like a business method that can only be used to group payments by credit cards. But it’s not! You could use this method to group the elements of any list by any of their properties, so this method should be abstracted and put inside the List class in such a way that it could be reused easily.
A very important part of functional programming consists in pushing abstraction to the limit. In the rest of this book, you’ll learn how to abstract many things so you never have to define them again. You will, for example, learn how to abstract loops so you won’t have to write loops ever again. And you’ll learn how to abstract parallelization in a way that will allow you to switch from serial to parallel processing just by selecting a method in the List class.
* Functional programming is programming with functions, returning values, and having no side effects.
* Functional programs are easy to reason about and easy to test.
* Functional programming offers a high level of abstraction and reusability.
* Functional programs are more robust than their imperative counterparts.
* Functional programs are safer in multithreading environments because they avoid shared mutable state.
* Functional Programming with Java 8
* Functional Programming for Beginners
* An Introduction to Functional Programming in Java 8: Part 3 - Streams