Why Functional Programming?
Functional programs contain no assignment statements, so variables, once given a value, never change. More generally, functional programs contain no side-effects at all. A function call can have no effect other than to compute its result. This eliminates a major source of bugs, and also makes the order of execution irrelevant—since no side-effect can change an expres- sion’s value, it can be evaluated at any time. This relieves the programmer of the burden of pre- scribing the flow of control. Since expressions can be evaluated at any time, one can freely replace variables by their values and vice versa—that is, programs are “referentially transpar- ent.” This freedom helps make functional programs more tractable mathematically than their conventional counterparts.
—John Hughes
“Why Functional Programming Matters”
I call it my billion-dollar mistake ... My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a bil- lion dollars of pain and damage in the last forty years.
—Tony Hoare
Program testing can be a very effective way to show the presence of bugs, but is hopelessly inad- equate for showing their absence.
—Edsger W. Dijkstra
Testing by itself does not improve software quality. Test results are an indicator of quality, but in and of themselves, they don’t improve it. Trying to improve software quality by increasing the amount of testing is like trying to lose weight by weighing yourself more often.
—Steve McConnell
The proper use of comments is to compensate for our failure to express ourselves in code.
—Robert C. Martin
In programming the hard part isn’t solving problems, but deciding what problems to solve.
—Paul Graham
Object oriented programming makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts.
—Michael Feathers
Licensed to
Functional Programming in Java
HOW FUNCTIONAL TECHNIQUES IMPROVE YOUR JAVA PROGRAMS
PIERRE-YVES SAUMONT
Licensed to MANNING
SHELTER ISLAND
1 ■
2 ■
3 ■
4 ■
5 ■
6 ■
7 ■
8 ■
9 ■
10 ■
11 ■
12 ■
13 ■
14 ■
15 ■
brief contents
What is functional programming? 1
Using functions in Java 16
Making Java more functional 57
Recursion, corecursion, and memoization 94 Data handling with lists 124
Dealing with optional data 151
Handling errors and exceptions 176 Advanced list handling 203
Working with laziness 230
More data handling with trees 256
Solving real problems with advanced trees 290
Handling state mutation in a functional way Functional input/output 342
Sharing mutable state with actors 370 Solving common problems functionally 394
321
Licensed to iii
Licensed to
contents
1.2 Writing useful programs with no side effects 4
1.3 How referential transparency makes programs safer 6
1.4 The benefits of functional programming 7
1.5 Using the substitution model to reason about programs 8
1.6 Applying functional principles to a simple example 9
1.7 Pushing abstraction to the limit 14
1.8 Summary 15
preface xiii acknowledgments xvi about this book xvii
1 What is functional programming? 1
1.1 What is functional programming? 2
2 Using functions in Java 16 2.1 What is a function? 17
Functions in the real world
17
Functional methods
anonymous classes
Polymorphic functions 29 ■ Simplifying the code by using lambdas 31
v
2.2 Functions in Java
22
Licensed to 23 ■ 28 ■
Java functional interfaces and Composing functions 29
vi
CONTENTS
2.3
Advanced function features 33
What about functions of several arguments? 33 ■ Applying curried functions 34 ■ Higher-order functions 35 Polymorphic higher-order functions 36 ■ Using anonymous functions 39 ■ Local functions 41 ■ Closures 42 Partial function application and automatic currying 44
Switching arguments of partially applied functions Recursive functions 49 ■ The identity function 51
Java 8 functional interfaces 52
2.4 2.5 2.6
Debugging with lambdas Summary 56
53
3 Making Java more functional 57
3.1 Making standard control structures functional 58
3.2 Abstracting control structures 59
Cleaning up the code 63 ■ An alternative to if ... else 3.3 Abstracting iteration 71
66
Abstracting an operation on lists with mapping 72
Creating lists 73 ■ Using head and tail operations 74 Functionally appending to a list 75 ■ Reducing and folding lists 75 ■ Composing mappings and mapping compositions 82
Applying effects to lists Building corecursive lists
3.4 Using the right types
82 ■ Approaching functional output 83 84
87
Problems with standard types 87 ■ Defining value types 90 The future of value types in Java 93
3.5 Summary 93
4 Recursion, corecursion, and memoization 94
4.1 Understanding corecursion and recursion 95
Exploring corecursive and recursive addition examples 95 Implementing recursion in Java 96 ■ Using tail call elimination 96 ■ Using tail recursive methods and
functions 97 ■ Abstracting recursion 97 ■ Using a drop-in replacement for stack-based recursive methods 101
4.2 Working with recursive functions 103
Using locally defined functions 104 ■ Making functions tail recursive 104 ■ Doubly recursive functions: the Fibonacci example 105 ■ Making the list methods stack-safe and recursive 108
Licensed to 48
CONTENTS vii
4.3 Composing a huge number of functions 111
4.4 Using memoization 114
Memoization in imperative programming 114 ■ Memoization in recursive functions 115 ■ Automatic memoization 117
4.5 Summary 123
5 Data handling with lists 124
5.1 How to classify data collections 125
Different types of lists 125 ■ Relative expected list performance 126 ■ Trading time against memory space, and time against complexity 127 ■ In-place mutation 128 Persistent data structures 129
5.2 An immutable, persistent, singly linked list implementation 130
5.3 Data sharing in list operations 133
More list operations 135
5.4 Using recursion to fold lists with higher-order functions 140
Heap-based recursive version of foldRight filtering lists 148
5.5 Summary 150
6 Dealing with optional data 151
6.1 Problems with the null pointer 152
6.2 Alternatives to null references 153
6.3 The Option data type 156
146 ■
Mapping and
Getting a value from an Option 158 ■ Applying functions to optional values 160 ■ Dealing with Option composition 161
Option use cases 163 ■ Other ways to combine options Composing List with Option 169
6.4 Miscellaneous utilities for Option 171
167
Testing for Some or None 171 ■ equals and hashcode 172
6.5 How and when to use Option 172
6.6 Summary 175
7 Handling errors and exceptions 176 7.1 The problems to be solved 177
Licensed to
viii
CONTENTS
7.2 7.3
7.4 7.5
7.6
The Either type 178
Composing Either 179
The Result type 181
Adding methods to the Result class 183
Result patterns 184
Advanced Result handling 191
Applying predicates 191 ■ Mapping failures factory methods 195 ■ Applying effects 196 ■ composition 199
Summary 202
192 ■
Advanced result
8 Advanced list handling 203
8.1 The problem with length 204
The performance problem 204 ■ The benefit of memoization 205
The drawbacks of memoization 205 ■ Actual performance
8.2 Composing List and Result 207
Methods on List returning Result 208 ■ Converting from List to Result 209
8.3 Abstracting common List use cases 212
Zipping and unzipping lists 212 ■ Accessing elements by their index 215 ■ Splitting lists 217 ■ Searching for sublists 221 ■ Miscellaneous functions for working with lists 222
8.4 Automatic parallel processing of lists 225
207
Not all computations can be parallelized 226 ■ Breaking
the list into sublists 226 ■ Processing sublists in parallel 227
8.5 Summary 229
9 Working with laziness 230
9.1 Understanding strictness and laziness 230
Java is a strict language 231 ■ The problem with strictness 232
9.2 Implementing laziness 233
9.3 Things you can’t do without laziness 234
9.4 Why not use the Java 8 Stream?
9.5 Creating a lazy list data structure Memoizing evaluated values 237 ■
235 236
Manipulating streams 241
Licensed to Adding
9.6 The true essence of laziness 243
Folding streams 245
9.7 Handling infinite streams 251
9.8 Avoiding null references and mutable fields
9.9 Summary 255
10 More data handling with trees 256 10.1 The binary tree 257
253
function 279 ■
10.6 Mapping trees
10.7 Balancing trees
Which fold implementation to choose 279
281 282
CONTENTS
ix
Balanced and unbalanced trees 258 ■ Size, height, and depth 258 ■ Leafy trees 259 ■ Ordered binary trees or binary search trees (BST) 259 ■ Insertion order 260 Tree traversal order 261
10.2 Implementing the binary search tree 263
10.3 Removing elements from trees 268
10.4 Merging arbitrary trees 270
10.5 Folding trees 275
Folding with two functions 276 ■ Folding with a single
Rotating trees 282
Balancing trees using the Day-Stout-Warren algorithm 285 Automatically balancing trees 287 ■ Solving the right problem 288
10.8 Summary 288
11 Solving real problems with advanced trees 290
11.1 Better performance and stack safety
with self-balancing trees 291
The basic tree structure 291 ■ Inserting an element into the red-black tree 295
11.2 A use case for the red-black tree: maps 300
Implementing Map 301 ■ Extending maps 303 Using Map with noncomparable keys 304
11.3 Implementing a functional priority queue 307
The priority queue access protocol 307 ■ Priority queue use cases 307 ■ Implementation requirements 308 ■ The leftist
Licensed to
x CONTENTS
heap data structure 308 ■ Implementing the leftist heap 309
Implementing the queue-like interface 313
11.4 A priority queue for noncomparable elements 314 11.5 Summary 319
12 Handling state mutation in a functional way 321
12.1 A functional random number generator
322
The random number generator interface 323 Implementing the random number generator 324
12.2 A generic API for handling state 327
Working with state operations 328 ■ Composing state operations 329 ■ Recursive state operations 331
12.3 Generic state handling 333
State patterns 334 ■ Building a state machine 335 When to use state and the state machine 340
12.4 Summary 341
13 Functional input/output 342
13.1 Applying effects in context 343
What are effects? 343 ■ Implementing effects 344 More-powerful effects for failures 346
13.2 Reading data 349
Reading data from the console 349 ■ Reading from a file 354 ■ Testing with input 355
13.3 Really functional input/output 356
How can input/output be made fully functional? 356 Implementing purely functional input/output 357 Combining IO 358 ■ Handling input with IO 359 Extending the IO type 361 ■ Making the IO type stack-safe 364
13.4 Summary 369
14 Sharing mutable state with actors 370 14.1 The actor model 371
Asynchronous messaging 372 ■ Handling parallelization 372 Handling actor state mutation 372
Licensed to
15.1 15.2
15.3
15.4
appendix A appendix B appendix C
index 440
Using assertions to validate data 395 Reading properties from file 399
Loading the property file 400 ■ Reading properties as strings 400 ■ Producing better error messages 402 Reading properties as lists 405 ■ Reading enum
values 406 ■ Reading properties of arbitrary types 407
Converting an imperative program: the XML reader 409
Listing the necessary functions 411 ■ Composing the functions and applying an effect 412 ■ Implementing the functions 412 Making the program even more functional 414 ■ Fixing the argument type problem 417 ■ Making the element-processing function a parameter 418 ■ Handling errors on element
names 420
Summary 421
Using Java 8 functional features 422 Monads 429
Where to go from here 434
CONTENTS xi 14.2 Building the actor framework 373
Limitations of this actor framework
framework interfaces 374 ■ The AbstractActor implementation 376
14.3 Putting actors to work 377
Implementing the ping-pong example 378 ■ A more serious example: running a computation in parallel 379 ■ Reordering the results 385 ■ Fixing the performance problem 388
14.4 Summary 393
15 Solving common problems functionally 394
Licensed to 374 ■ Designing the actor
Licensed to
preface
Writing programs is fun and rewarding. Programming is an activity that many people would do for fun, and yet are paid for. In this sense, a programmer is a bit like an actor, a musician, or a professional football player. It seems like a dream until you, as a programmer, begin to have real responsibilities. Writing games or office applications isn’t really a big deal from this point of view. If your application has a bug, you simply fix it and release a new version. But if you write applications that people depend on, and if you can’t simply release a new version and have your users install it themselves, it’s another story. Of course, Java isn’t meant for writing applications for monitoring nuclear plants or flying airplanes, or any system in which a simple bug could put human life at risk. But if your application is used to manage internet backbones, you wouldn’t like a nasty bug to be discovered one day before the Olympic Games open, causing a TV transmission failure for a whole country. For such applications, you want to be sure that your program can be proven correct.
Most imperative programs can’t be proven correct. Tests only allow us to prove programs incorrect when they fail. Successful tests don’t prove much. What you release are programs that you weren’t able to prove incorrect. With single-threaded programs, extensive tests may let you show that your code is mostly correct. But with multithreaded applications, the number of possible condition combinations makes that impossible. Clearly, we need a different way to write programs. Ideally, it would be a way that allows us to prove that a program is correct. Because this is generally not fully possible, a good compromise is a clear separation between parts of the program that can be proven correct and parts that can’t. This is what functional programming techniques offer.
xiii
Licensed to
xiv
PREFACE
Functional programming has about as many definitions as there are functional programmers. Some say that functional programming is programming with functions. This is true, but it doesn’t help you understand the benefits of this programming par- adigm. More important is the idea that functional programming involves pushing abstraction to the limit. This allows a clear separation between the parts of a program that can be proven correct and the other parts whose output depends on external conditions. This way, functional programs are programs that are less prone to bugs, and in which bugs can only reside in specific, restricted areas.
Many techniques can be employed to reach this goal. The use of immutable data, although not specific to functional programming, is such a technique. If data can’t change, you won’t have any (bad) surprises, no stale or corrupted data, no race condi- tions, no need for locking on concurrent accesses, and no risk of deadlocks. Immutable data can be shared without risk. You don’t need to make defensive copies, and you don’t risk forgetting to do so. Another technique is abstracting control struc- tures so that you don’t have to write the same structures again and again, multiplying the risk of messing with loop indexes and exit conditions. Completely removing the use of null references (whether implicit or explicit) will free you from the infamous NPE (NullPointerException). With all these techniques (and more), you can be confi- dent that if your program compiles, it’s correct (meaning that it has no implementa- tion bugs). This doesn’t remove all possibility of bugs, but it makes things much safer.
Computers have used the imperative paradigm from the beginning, based on mutating values in registers. Java, like many other programming languages known as “imperative languages,” seems to rely heavily on this paradigm, but this isn’t essential. If you’re an experienced Java programmer, you might be surprised to hear that you can write useful programs without ever changing the value of a variable. This isn’t a mandatory condition for functional programming, but it’s so comfortable that func- tional programmers nearly always use immutable data. You might also have difficulty believing that you can write applications without ever using an if ... else structure or a while or for loop. Again, avoiding such structures isn’t a condition for using the functional paradigm, but you can avoid them if you want, and this leads to much safer programs. So even if Java is generally seen as an “imperative language,” it’s not. No lan- guage is imperative, and no language is functional. Believing that they are is like think- ing that English is better for business texts while Italian would be better for singing opera, French for love poetry, and German for philosophy (or whatever combinations you can imagine). Differences may exist, but they’re mostly cultural, and the same is true for programming languages. Java is an imperative language because most Java programmers are imperative programmers, and the Java culture is mostly imperative. In contrast, Haskell programs are generally written in a functional style because pro- grammers choose this language with functional programming in mind. But it’s possi- ble to write imperative programs in Haskell, and it’s possible to write functional programs in Java. The difference is that Haskell is more “functional-friendly” than Java.
Licensed to
PREFACE xv
So the question is, “Should you use Java for functional programming”? Surpris- ingly (given the subject of this book) the answer is no. With the freedom to choose any language, I’ll say that you shouldn’t chose Java for this purpose. But you generally won’t have this freedom. Most of the negative comments I received when writing arti- cles about using Java for functional programming were along the lines of “You should not use Java for this. This is not the way Java was intended to be used,” or “Why are you using Java for this? Better to use Haskell, or Scala, or whatever.”
In reality, you generally don’t have a choice of language. If you work in a company, you probably have to use the corporate language, or at least the one chosen by your team for the project you’re working on. Haskell is never an option from this point of view. Often, you’ll have no choice but to use Java. And if you’re in a position to choose the language, you likely won’t have any choice besides using a language you know, or using a language that allows the reuse of some legacy code, or a language that suits the environment, or some other condition. This book is aimed at you, the Java pro- grammer who has no real choice other than using Java, although you want to benefit from the safety of functional programming.
Using functional programming techniques in Java will often cause you to go against so-called “best practices.” Many of these practices are, in fact, useless, and some are very bad practices indeed. Never catching errors is one of them. As a Java programmer, you probably learned that you shouldn’t be catching OOME (Out Of Memory Error) or other kinds of errors you can’t deal with. Maybe you even learned that you shouldn’t catch NPEs (NullPointerExceptions) because they indicate bugs, and you should let the application crash and fix it. Unfortunately, neither OOME nor NPE will crash the application. They’ll only crash the thread in which they occur, leaving the application in some indeterminate state. Even if they occur in the main thread, they’ll possibly fail to crash the application if some non-daemon threads are running. This “best practice” was true when all applications were single-threaded. It’s now a very bad practice. You should catch all exceptions, although possibly not in a try ... catch block. In func- tional programming, the mantra is, “Always catch, never throw.”
There are many other best practices that will be challenged during our functional programming journey. One of them, although not directly related to Java or impera- tive programming, is, “Don’t reinvent the wheel.” Think about it. Once, someone invented the wheel. At that time, it was probably something roughly circular made of some rigid material and turning on an axle. The wheel has been reinvented many times since then. If it hadn’t, you’d have no cars, no trains, and nearly nothing using wheels. So you should continue trying to reinvent the wheel again and again. Not only will this give us better wheels in the future, but it’s challenging, rewarding, and fun. (And if you believe that modern cars have circular wheels, you’d better think again. No car could ever run on circular wheels!)
Licensed to
acknowledgments
I would like to thank the many people who participated in making this book possible. First, a big thank you to my developmental editor, Marina Michaels. Besides your
wonderful work on the manuscript, it’s been a real pleasure to work with you.
A big thank you, too, to Mark Elston, my technical editor, and to Alessandro Campeis, my technical proofreader, both of whom helped me make this book much
better than I could have done alone.
To all the reviewers, MEAP readers, and everyone else who provided feedback and
comments, thank you! This book would not be what it is today without your help. Spe- cifically, I’d like to thank the following people who all took the time to review and comment on the book: Aditya Kumar, Al Krinker, Andy Kirsch, Andy Knight, Anthony Moralez, Arun Allamsetty, Barry Kern, Boris Vasile, Bruce Hernandez, Charles Feduke, Chris Kirk, David Drummond, Davide Fiorentino lo Regio, Erwin van Eijk, Gualtiero Testa, Ivan Milosavljevic ́, Jan Vorwerk, Jérôme Baton, Joshua McAdams, Julian Templeman, Maria Gemini, Norbert Kuchenmeister, Philippe Charrière, Piotr Bzdyl, Rambabu Posa, Sebastian Hähnel, Sebastian Metzger, Simeon Leyzerzon, Tarin Gamberini, Ursin Stauss, William Wheeler, Zach Schwartz and Zorodzayi Mukuya.
xvi
Licensed to
about this book
This isn’t a book about Java. This book is about functional programming, which is a different way to write software programs. “Different” means different from the “tradi- tional” way of writing software, which is called the imperative paradigm. This book is about applying the functional paradigm to Java programming.
There’s no such thing as a “functional language.” There are only languages that are more-or-less functional-friendly. Although I use Java in this book, you can apply all the principles I teach to any other language. Only the way in which you implement these principles would be different. You can write functional programs in any lan- guage, even those said not to be functional at all; you can similarly write imperative programs with the most functional-friendly languages.
With the release of Java 8, some functional features have been added to the Java language. But just as this book isn’t about Java, it’s also not about these specific Java 8 features. In this book, I make heavy use of some of these features, and I mostly ignore others. If your goal is to learn how to use the functional features of Java 8, this is not the right book. Urma, Fusco, and Mycroft’s Java 8 in Action (Manning, 2014) would be a much better choice.
On the other hand, if you want to learn what functional programming is, how to build functional data structures, and how the functional programming paradigm will help you write better programs (sometimes using the Java 8 features and sometimes avoiding them), this is the book for you.
Audience
This book is intended for readers with some programming experience in Java. A good understanding of Java generics is necessary. If you find yourself not understanding a
xvii
Licensed to
xviii
ABOUT THIS BOOK
Java construction (such as generic constants implemented as methods, or parameter- ized method calls), don’t be afraid: I’ll explain what they mean and why they’re needed.
You don’t need to have prior experience in functional programming, or to be aware of the mathematical theory that underlies it. Chapter 2 will act as a reminder of what a function is, and that’s it. No other math will be used.
I present all functional techniques in relation to their imperative counterparts, so I expect you to have experience with imperative programming in Java.
How to use this book
This book is intended to be read sequentially, because each chapter builds upon the concepts learned in the previous ones. The only exceptions are chapters 14 and 15, in which what you’ll learn in chapters 12 and 13 isn’t used. This means you can skip chapters 12 and 13 if you want; they present more-advanced techniques that are useful to know but that you might prefer not to use in your own programs.
I’ve used the word “read,” but this book isn’t intended to just be read. Very few sec- tions are theory only. To get the most out of this book, read it at your computer key- board, solving the exercises as you go. Each chapter includes a number of exercises with the necessary instructions and hints to help you arrive at the solution. All the code is available as a separate free download from GitHub (http://github.com/fpinjava/ fpinjava) and from the publisher’s website at https://www.manning.com/books/ functional-programming-in-java. Each exercise comes with a proposed solution and JUnit tests that you can use to verify that your solution is correct.
The code comes with all the necessary elements for the project to be imported into IntelliJ (recommended), NetBeans, or Eclipse, although at the time of this writing, Eclipse (Mars 4.5.1) is not yet fully compatible with Java 8. Projects may be imported “from source” or using Gradle. Any version of Gradle may be used, because Gradle is able to download the correct version automatically.
Please note that you’re not expected to understand most of the concepts pre- sented in this book by just reading the text. Doing the exercises is probably the most important part of the learning process, so I encourage you not to skip any exercises. Some might seem quite difficult, and you might be tempted to look at the proposed solutions. It’s perfectly OK to do so, but you should then come back to the exercise and do it without looking at the solution. If you only read the solution, you’ll probably have problems later trying to solve more-advanced exercises.
This approach doesn’t require much tedious typing, because you have nearly noth- ing to copy. Most exercises consist of writing implementations for methods, for which you are given the environment and the method signature. No exercise is longer than a dozen lines of code; the majority are around four or five lines long.
Once you finish an exercise (which means when your implementation compiles), just run the corresponding test to verify that it’s correct.
Licensed to
ABOUT THIS BOOK xix
One important thing to note is that each exercise is self-contained with regard to the rest of the chapter, so code created inside a chapter is duplicated from one exer- cise to the next. This is necessary because each exercise is often built upon the preced- ing one, so although the same class might be used, implementations differ. As a consequence, you shouldn’t look at an exercise before you complete the previous ones, because you’ll see the solutions to yet-unsolved exercises.
You can download the code as an archive, or you can clone it using Git. I highly recommend cloning, since the code is subject to change, and it’s much more efficient to update your code with a simple pull command than to re-download the complete archive.
The code for exercises is organized in modules with names that more or less reflect the chapter titles, rather than the chapter numbers. As a result, IDEs will sort them alphabetically, rather than in the order in which they appear in the book. To help you figure out which module corresponds to each chapter, I’ve provided a list of the chapters with the corresponding module names in the README file accompany- ing the code (http://github.com/fpinjava/fpinjava).
Setting expectations
Functional programming is no more difficult than imperative programming. It’s just different. You can solve the same problems with both paradigms, but translating from one to the other can sometimes be inefficient. Learning functional programming is like learning a foreign language. Just as you can’t efficiently think in one language and translate to another, you can’t think imperatively and translate your code to the functional approach. And just as you have to learn to think in a new language, you have to learn to think functionally. Learning to think functionally doesn’t come with reading alone; it comes with writing code. So you have to practice.
This is why I don’t expect you to understand what’s in this book just by reading it, and why I provide so many exercises; you must do the exercises to fully grasp the con- cepts of functional programming. This isn’t because the topic is so complex that it isn’t possible to understand it through reading alone, but because if you could understand it just by reading (without doing the exercises), you probably wouldn’t need this book.
For all these reasons, the exercises are key to getting the most out of this book. I encourage you to try solving each exercise before you continue reading. If you don’t find a solution, try again rather than going directly to the solution I provide. If you have a hard time understanding something, ask questions on the forum (see the next section). Asking questions and getting answers on the forum will not only help you, it will also help the person answering the question (along with others who have the same question). We all learn by answering questions (mostly our own questions, by the way) much more than by asking them.
Licensed to
xx
ABOUT THIS BOOK
Author Online
Purchase of Functional Programming in Java includes free access to a private web forum run by Manning Publications, where you can make comments about the book, ask technical questions, and receive help from the author and other users, or even pro- vide help to other users. To access the forum and subscribe to it, point your web browser to https://forums.manning.com/forums/functional-programming-in-java. This Author Online page provides information on how to get on the forum once you’re registered, what kind of help is available, and the rules of conduct on the forum.
Manning’s commitment to our readers is to provide a venue where a meaningful dialog among individual readers and between readers and the authors can take place. It’s not a commitment to any specific amount of participation on the part of the authors, whose contribution to the forum remains voluntary. I, as the author of this book, will be monitoring this forum and will answer questions as promptly as possible.
The Author Online forum and the archives of previous discussions will be accessi- ble from the publisher’s website as long as the book is in print.
Licensed to
What is functional programming?
This chapter covers
The benefits of functional programming
Problems with side effects
How referential transparency makes programs safer
Reasoning about programs with the substitution model
Making the most of abstraction
Not everybody agrees on a definition for functional programming (FP). In general terms, functional programming is a programming paradigm, and it’s about pro- gramming 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,” pub- lished in 1990, John Hughes writes the following:
1
Licensed to
2
CHAPTER 1 What is functional programming?
1.1
Functional programs contain no assignment statements, so variables, once given a value, never change. More generally, functional programs contain no side effects at all. A function call can have no effect other than to compute its result. This eliminates a major source of bugs, and also makes the order of execution irrelevant—since no side effect can change an expression’s value, it can be evaluated at any time. This relieves the programmer of the burden of prescribing the flow of control. Since expressions can be evaluated at any time, one can freely replace variables by their values and vice versa—that is, programs are “referentially transparent.” This freedom helps make functional programs more tractable mathematically than their conventional counterparts.1
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
First-class functions
Anonymous functions
Closures
Currying
Lazy evaluation
Parametric polymorphism Algebraic data types
Although it is true that most functional languages do use a number of these tech- niques, you may find, for each of them, examples of functional programming lan- guages 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 pro- gramming 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,
1 John Hughes, “Why Functional Programming Matters,” from D. Turner, ed., Research Topics in Functional Pro- gramming (Addison-Wesley, 1990), 17–42, www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf.
Licensed to
What is functional programming? 3
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 test- ing. For example, an addition program for adding two positive values a and b might be represented by the following pseudo code:
if b == 0, return a
else increment a and decrement b start again with the new a and b
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.
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 differ- ent 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.)
b == 0 ?
No
Add 1 to a
Remove 1 from b
Yes Return a
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.
Licensed to
4
CHAPTER 1 What is functional programming?
One major difference between imperative programming and FP is that in FP there are
no side effects. This means, among other things,
No mutation of variables
No printing to the console or to any device
No writing to files, databases, networks, or whatever No exception throwing
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
Take an argument (a single one, as you’ll see later)
Do mysterious things inside, such as mutating variables and a lot of imperative-
style stuff, but with no effect observable from outside
Return a (single) value
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 appli- cation, which is a somewhat observable side effect. And it will cause writing to mem- ory, 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 dis- playing the result on a screen, writing it to a file or database, or sending it over a net- work. 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 impera- tive style, it might yet be functional, depending on how it’s implemented. Imagine this program is implemented in Java as follows:
1.2
Licensed to
Writing useful programs with no side effects 5
public static int add(int a, int b) {
while (b > 0) {
a++;
b--; }
return a; }
This program is fully functional. It takes an argument, which is the pair of integers a and b, it returns a value, and it has absolutely no other observable effect. That it mutates variables doesn’t contradict the requirements, because arguments in Java are passed by value, so the mutations of the arguments aren’t visible from outside. You can then choose to apply an effect, such as displaying the result or using the result for another computation.
Note that although the result might not be correct (in case of an arithmetic over- flow), that’s not in contradiction with having no side effects. If values a and b are too big, the program will silently overflow and return an erroneous result, but this is still functional. On the other hand, the following program is not functional:
public static int div(int a, int b) {
return a / b;
}
Although this program doesn’t mutate any variables, it throws an exception if b is equal to 0. Throwing an exception is a side effect. In contrast, the following imple- mentation, although a bit stupid, is functional:
public static int div(int a, int b) {
return (int) (a / (float) b);
}
This implementation won’t throw an exception if b is equal to 0, but it will return a special result. It’s up to you to decide whether it’s OK or not for your function to return this specific result to mean that the divisor was 0. (It’s probably not!)
Throwing an exception might be an intentional or unintentional side effect, but it’s always a side effect. Often, though, in imperative programming, side effects are wanted. The simplest form might look like this:
public static void add(int a, int b) {
while (b > 0) {
a++;
b--; }
System.out.println(a);
}
This program doesn’t return a value, but it prints the result to the console. This is a desired side effect.
Licensed to
6
CHAPTER 1 What is functional programming?
Note that the program could alternatively both return a value and have some
intentional side effects, as in the following example:
public static int add(int a, int b) {
log(String.format("Adding %s and %s", a, b));
while (b > 0) {
a++;
b--; }
log(String.format("Returning %s", a));
return a; }
This program isn’t functional because it uses side effects for logging.
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 inter- est to programmers:
It’s self-contained. It doesn’t depend on any external device to work. You can use it in any context—all you have to do is provide a valid argument.
It’s deterministic, which means it will always return the same value for the same argument. With referentially transparent code, you won’t be surprised. It might return a wrong result, but at least, for the same argument, this result will never change.
It will never throw any kind of Exception. It might throw errors, such as OOME (out-of-memory error) or SOE (stack-overflow error), but these errors mean that the code has a bug, which is not a situation you, as a programmer, or the users of your API, are supposed to handle (besides crashing the application and eventually fixing the bug).
It won’t create conditions causing other code to unexpectedly fail. For exam- ple, it won’t mutate arguments or some other external data, causing the caller to find itself with stale data or concurrent access exceptions.
It won’t hang because some external device (whether database, file system, or network) is unavailable, too slow, or simply broken.
Figure 1.2 illustrates the difference between a referentially transparent program and one that’s not referentially transparent.
1.3
Licensed to
The benefits of functional programming 7
Objects
Program
Input (argument) Output (result)
Database
Keyboard
Screen
File
A referentially transparent program doesn't interfere with the outside world apart from taking an argument as input and outputting a result. Its result only depends on its argument.
Exception
Objects
Database
Program
Input (argument) Output (result)
Keyboard
Screen
File
A program that isn’t referentially transparent may read data from or write it to elements in the outside world, log to file, mutate external objects, read from keyboard, print to screen, and so on. Its result is unpredictable.
Figure 1.2 Comparing a program that’s referentially transparent to one that’s not
1.4 The benefits of functional programming
From what I’ve just said, you can likely guess the many benefits of functional pro- gramming:
Functional programs are easier to reason about because they’re deterministic. One specific input will always give the same output. In many cases, you might be able to prove your program correct rather than extensively testing it and still being uncertain whether it will break under unexpected conditions.
Functional programs are easier to test. Because there are no side effects, you don’t need mocks, which are generally required to isolate the programs under test from the outside.
Licensed to
8
CHAPTER 1 What is functional programming?
1.5
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 depen- dent 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.
= 26
The expression 3 x 2 may be replaced with its value: = 26
The expression 4 x 5 may be replaced with its value: = 26 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:
public static void main(String[] args) {
int x = add(mult(2, 3), mult(4, 5));
}
public static int add(int a, int b) {
log(String.format("Returning %s as the result of %s + %s", a + b, a, b));
return a + b; }
public static int mult(int a, int b) {
return a * b;
}
Functional programs are more modular because they’re built from functions that have only input and output; there are no side effects to handle, no excep- tions to catch, no context mutation to deal with, no shared mutable state, and no concurrent modifications.
Functional programming makes composition and recombination much easier. To write a functional program, you have to start by writing the various base func- tions you need and then combine these base functions into higher-level ones, repeating the process until you have a single function corresponding to the pro- gram you want to build. As all these functions are referentially transparent, they can then be reused to build other programs without any modifications.
3x2
+
4x5
+
4x5
6
+
20
Licensed to
Applying functional principles to a simple example 9
public static void log(String m) {
System.out.println(m);
}
Replacing mult(2, 3) and mult(4, 5) with their respective return values doesn’t
change the signification of the program:
int x = add(6, 20);
In contrast, replacing the call to the add function with its return value changes the sig- nification of the program, because the log method will no longer be called, and no logging will happen. This might be important or not; in any case, it changes the result of the program.
1.6 Applying functional principles to a simple example
As an example of converting an imperative program into a functional one, we’ll con- sider a very simple program representing the purchase of a donut with a credit card.
Listing 1.1 A Java program with side effects
public class DonutShop {
public static Donut buyDonut(CreditCard creditCard) {
Donut donut = new Donut();
creditCard.charge(Donut.price); B Charges the credit card as a side effect return donut; C Returns the donut
} }
In this code, the charging of the credit card is a side effect B. Charging a credit card probably consists of calling the bank, verifying that the credit card is valid and autho- rized, and registering the transaction. The function returns the donut C.
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
public class Payment {
public final CreditCard creditCard;
public final int amount;
Licensed to
10
CHAPTER 1 What is functional programming?
public Payment(CreditCard creditCard, int amount) {
this.creditCard = creditCard;
this.amount = amount;
} }
This class contains the necessary data to represent the payment, which consists of a credit card and the amount to charge. Because the buyDonut method must return both a Donut and a Payment, you could create a specific class for this, such as Purchase:
public class Purchase {
public Donut donut;
public Payment payment;
public Purchase(Donut donut, Payment payment) {
this.donut = donut;
this.payment = payment;
} }
You’ll often need such a class to hold two (or more) values, because functional pro- gramming replaces side effects with returning a representation of these effects.
Rather than creating a specific Purchase class, you’ll use a generic one that you’ll call Tuple. This class will be parameterized by the two types it will contain (Donut and Payment). The following listing shows its implementation, as well as the way it’s used in the DonutShop class.
Listing 1.3 The Tuple class public class Tuple {
public final T _1;
public final U _2;
public Tuple(T t, U u) {
this._1 = t;
this._2 = u;
} }
public class DonutShop {
public static Tuple buyDonut(CreditCard creditCard) { Donut donut = new Donut();
Payment payment = new Payment(creditCard, Donut.price);
return new Tuple<>(donut, payment);
} }
Note that you’re no longer concerned (at this stage) with how the credit card will actually be charged. This adds some freedom to the way you build your application. You could still process the payment immediately, or you could store it for later pro- cessing. You could even combine stored payments for the same card and process them
Licensed to
Applying functional principles to a simple example 11
in a single operation. This would allow you to save money by minimizing the bank fees for the credit card service.
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 contra- dict 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
package com.fpinjava.introduction.listing01_04;
public class Payment {
public final CreditCard creditCard;
public final int amount;
public Payment(CreditCard creditCard, int amount) {
this.creditCard = creditCard;
this.amount = amount;
}
public Payment combine(Payment payment) {
if (creditCard.equals(payment.creditCard)) {
return new Payment(creditCard, amount + payment.amount);
} else {
throw new IllegalStateException("Cards don't match.");
}
} }
Of course, the combine method wouldn’t be very efficient for buying several donuts at once. For this use case, you could simply replace the buyDonut method with buy- Donuts(intn,CreditCardcreditCard), as shown in the following listing. This method returns a Tuple, Payment>.
Listing 1.5 Buying multiple donuts at once
package com.fpinjava.introduction.listing01_05;
import static com.fpinjava.common.List.fill; import com.fpinjava.common.List;
import com.fpinjava.common.Tuple;
public class DonutShop {
public static Tuple buyDonut(final CreditCard cCard) { return new Tuple<>(new Donut(), new Payment(cCard, Donut.price));
}
public static Tuple, Payment> buyDonuts(final int quantity, final CreditCard cCard) {
return new Tuple<>(fill(quantity, () -> new Donut()),
new Payment(cCard, Donut.price * quantity));
Licensed to