Object-Oriented vs. Functional Programming · that programming is basically just math, and that now...

46
Richard Warburton Bridging the Divide Between Opposing Paradigms Object-Oriented vs. Functional Programming

Transcript of Object-Oriented vs. Functional Programming · that programming is basically just math, and that now...

Page 1: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Richard Warburton

Bridging the Divide Between Opposing Paradigms

Object-Oriented vs. Functional Programming

Page 2: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers
Page 3: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Richard Warburton

Object-Oriented vs.Functional Programming

Bridging the Divide BetweenOpposing Paradigms

Page 4: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

978-1-491-93342-8

[LSI]

Object-Oriented vs. Functional Programmingby Richard Warburton

Copyright © 2016 O’Reilly Media. All rights reserved.

Printed in the United States of America.

Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA95472.

O’Reilly books may be purchased for educational, business, or sales promotional use.Online editions are also available for most titles (http://safaribooksonline.com). Formore information, contact our corporate/institutional sales department:800-998-9938 or [email protected] .

Editor: Brian FosterProduction Editor: Nicholas AdamsCopyeditor: Amanda KerseyProofreader: Nicholas Adams

Interior Designer: David FutatoCover Designer: Randy ComerIllustrator: Rebecca Demarest

November 2015: First Edition

Revision History for the First Edition2015-10-30: First Release

While the publisher and the author have used good faith efforts to ensure that theinformation and instructions contained in this work are accurate, the publisher andthe author disclaim all responsibility for errors or omissions, including without limi‐tation responsibility for damages resulting from the use of or reliance on this work.Use of the information and instructions contained in this work is at your own risk. Ifany code samples or other technology this work contains or describes is subject toopen source licenses or the intellectual property rights of others, it is your responsi‐bility to ensure that your use thereof complies with such licenses and/or rights.

Page 5: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Table of Contents

Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii

1. Lambdas: Parameterizing Code by Behavior. . . . . . . . . . . . . . . . . . . . . 1Why Do I Need to Learn About Lambda Expressions? 1The Basics of Lambda Expressions 2Summary 5

2. SOLID Principles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7Lambda-Enabled SOLID Principles 7The Single-Responsibility Principle 7The Open/Closed Principle 10The Liskov Substitution Principle 14The Interface-Segregation Principle 15The Dependency-Inversion Principle 17Summary 21

3. Design Patterns. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23Functional Design Patterns 23The Command Pattern 23Strategy Pattern 28Summary 31

4. Conclusions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33Object-Oriented vs. Functional Languages 33Programming Language Evolution 34

v

Page 6: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers
Page 7: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Introduction

One of my favorite professional activities is speaking at softwareconferences. It’s great fun because you get to meet developers whoare passionate about their craft, and it gives you as a speaker theopportunity to share knowledge with them.

A talk that I’ve enjoyed giving recently is called “Twins: FP andOOP.” I’ve given it at a number of conferences and user group ses‐sions, and I’ve even had the pleasure of giving it as O’Reilly webcast.Developers enjoy the talk both because it has a large number of ref‐erences to the film “Twins” and because it discusses one of the age-old relationships between functional and object-oriented program‐ming.

There’s only so much you can say in a conference talk though, so Iwas really excited when Brian Foster from O’Reilly contacted me toask if I wanted to expand upon the topic in a report. You can alsothink of this as a short followup to my earlier O’Reilly publishedbook Java 8 Lambdas (O’Reilly).

You can watch the talk delivered at a conference online or deliveredas an O’Reilly webcast.

What Object-Oriented and FunctionalProgrammers Can Learn From Each OtherBefore we get into the technical nitty-gritty of lambdas and designpatterns, let’s take a look at the technical communities. This willexplain why comparing the relationship between functional andobject-oriented is so important and relevant.

vii

Page 8: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

If you’ve ever read Hacker News, a programming subreddit, or anyother online forum, you might have noticed there’s often a touch offriction between functional programmers and developers practicingthe object-oriented style. They often sound like they’re talking in adifferent language to each other, sometimes even going so far as tothrow the odd snarky insult around.

On the one hand, functional programmers can often look down ontheir OO counterparts. Functional programs can be very terse andelegant, packing a lot of behavior into very few lines of code. Func‐tional programmers will make the case that in a multicore world,you need to avoid mutable state in order to scale out your programs,that programming is basically just math, and that now is the time foreveryone to think in terms of functions.

Object-oriented programmers will retort that in actual businessenvironments, very few programmers use functional languages.Object-oriented programming scales out well in terms of develop‐ers, and as an industry, we know how to do it. While programmingcan be viewed as a discipline of applied math, software engineeringrequires us to match technical solutions to business problems. Thedomain modelling and focus on representing real-world objects thatOOP encourages in developers helps narrow that gap.

Of course, these stereotypes are overplaying the difference. Bothgroups of programmers are employed to solve similar businessproblems. Both groups are working in the same industry. Are theyreally so different?

I don’t think so, and I think there’s a lot that we can learn from eachother.

What’s in This ReportThis report makes the case that a lot of the constructs of goodobject-oriented design also exist in functional programming. Inorder to make sure that we’re all on the same page, Chapter 1explains a little bit about functional programming and the basics oflambda expressions in Java 8.

In Chapter 2, we take a look at the SOLID principles, identified byRobert Martin, and see how they map to functional languages andparadigms. This demonstrates the similarity in terms of higher-levelconcepts.

viii | Introduction

Page 9: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

In Chapter 3, we look at some behavioral design patterns. Designpatterns are commonly used as a vocabulary of shared knowledgeamongst object-oriented programmers. They’re also often criticizedby functional programmers. Here we’ll look at how some of themost common object-oriented design patterns exist in the func‐tional world.

Most of the examples in this guide are written in the Java program‐ming language. That’s not to say that Java is the only language thatcould have been used or that it’s even a good one! It is perfectly ade‐quate for this task though and understood by many people. Thisguide is also motivated by the release of Java 8 and its introductionof lambda expressions to the language. Having said all that, a lot ofprinciples and concepts apply to many other programming lan‐guages as well, and I hope that whatever your programming lan‐guage is, you take something away.

Introduction | ix

Page 10: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers
Page 11: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

CHAPTER 1

Lambdas: Parameterizing Code byBehavior

Why Do I Need to Learn About LambdaExpressions?Over the next two chapters, we’re going to be talking in depth aboutthe relationship between functional and object-oriented program‐ming principles, but first let’s cover some of the basics. We’re goingto talk about a couple of the key language features that are related tofunctional programming: lambda expressions and method refer‐ences.

If you already have a background in functional pro‐gramming, then you might want to skip this chapterand move along to the next one.

We’re also going to talk about the change in thinking that theyenable which is key to functional thinking: parameterizing code bybehavior. It’s this thinking in terms of functions and parameterizingby behavior rather than state which is key to differentiating func‐tional programming from object-oriented programming. Theoreti‐cally this is something that we could have done in Java before withanonymous classes, but it was rarely done because they were sobulky and verbose.

1

Page 12: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

We shall also be looking at the syntax of lambda expressions in theJava programming language. As I mentioned in the Introduction, alot of these ideas go beyond Java; we are just using Java as a lingua-franca: a common language that many developers know well.

The Basics of Lambda ExpressionsWe will define a lambda expression as a concise way of describing ananonymous function. I appreciate that’s quite a lot to take in at once,so we’re going to explain what lambda expressions are by workingthrough an example of some existing Java code. Swing is a platform-agnostic Java library for writing graphical user interfaces (GUIs). Ithas a fairly common idiom in which, in order to find out what youruser did, you register an event listener. The event listener can thenperform some action in response to the user input (seeExample 1-1).

Example 1-1. Using an anonymous inner class to associate behaviorwith a button click

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("button clicked"); }});

In this example, we’re creating a new object that provides an imple‐mentation of the ActionListener class. This interface has a singlemethod, actionPerformed, which is called by the button instancewhen a user actually clicks the on-screen button. The anonymousinner class provides the implementation of this method. InExample 1-1, all it does is print out a message to say that the buttonhas been clicked.

This is actually an example of behavior parameteriza‐tion—we’re giving the button an object that representsan action.

Anonymous inner classes were designed to make it easier for Javaprogrammers to represent and pass around behaviors. Unfortu‐nately, they don’t make it easy enough. There are still four lines of

2 | Chapter 1: Lambdas: Parameterizing Code by Behavior

Page 13: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

boilerplate code required in order to call the single line of importantlogic.

Boilerplate isn’t the only issue, though: this code is fairly hard toread because it obscures the programmer’s intent. We don’t want topass in an object; what we really want to do is pass in some behavior.In Java 8, we would write this code example as a lambda expression,as shown in Example 1-2.

Example 1-2. Using a lambda expression to associate behavior with abutton click

button.addActionListener(event -> System.out.println("button clicked"));

Instead of passing in an object that implements an interface, we’repassing in a block of code—a function without a name. event is thename of a parameter, the same parameter as in the anonymousinner class example. -> separates the parameter from the body of thelambda expression, which is just some code that is run when a userclicks our button.

Another difference between this example and the anonymous innerclass is how we declare the variable event. Previously, we needed toexplicitly provide its type—ActionEvent event. In this example, wehaven’t provided the type at all, yet this example still compiles. Whatis happening under the hood is that javac is inferring the type of thevariable event from its context—here, from the signature ofaddActionListener. What this means is that you don’t need toexplicitly write out the type when it’s obvious. We’ll cover this infer‐ence in more detail soon, but first let’s take a look at the differentways we can write lambda expressions.

Although lambda method parameters require less boil‐erplate code than was needed previously, they are stillstatically typed. For the sake of readability and famili‐arity, you have the option to include the type declara‐tions, and sometimes the compiler just can’t work itout!

Method ReferencesA common idiom you may have noticed is the creation of a lambdaexpression that calls a method on its parameter. If we want a lambda

The Basics of Lambda Expressions | 3

Page 14: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

expression that gets the name of an artist, we would write the fol‐lowing:

artist -> artist.getName()

This is such a common idiom that there’s actually an abbreviatedsyntax for this that lets you reuse an existing method, called amethod reference. If we were to write the previous lambda expressionusing a method reference, it would look like this:

Artist::getName

The standard form is Classname::methodName. Remember that eventhough it’s a method, you don’t need to use brackets because you’renot actually calling the method. You’re providing the equivalent of alambda expression that can be called in order to call the method.You can use method references in the same places as lambda expres‐sions.

You can also call constructors using the same abbreviated syntax. Ifyou were to use a lambda expression to create an Artist, you mightwrite:

(name, nationality) -> new Artist(name, nationality)

We can also write this using method references: Artist::new

This code is not only shorter but also a lot easier to read.Artist::new immediately tells you that you’re creating a newArtist without your having to scan the whole line of code. Anotherthing to notice here is that method references automatically supportmultiple parameters, as long as you have the right functional inter‐face.

It’s also possible to create arrays using this method. Here is how youwould create a String array:

String[]::new

When we were first exploring the Java 8 changes, a friend of minesaid that method references “feel like cheating.” What he meant wasthat, having looked at how we can use lambda expressions to pass

4 | Chapter 1: Lambdas: Parameterizing Code by Behavior

Page 15: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

code around as if it were data, it felt like cheating to be able to refer‐ence a method directly.

In fact, method references are really making the concept of first-class functions explicit. This is the idea that we can pass behavioraround and treat it like another value. For example, we can composefunctions together.

SummaryWell, at one level we’ve learnt a little bit of new syntax that has beenintroduced in Java 8, which reduces boilerplate for callbacks andevent handlers. But actually there’s a bigger picture to these changes.We can now reduce the boilerplate around passing blocks of behav‐ior: we’re treating functions as first-class citizens. This makesparameterizing code by behavior a lot more attractive. This is key tofunctional programming, so key in fact that it has an associatedname: higher-order functions.

Higher-order functions are just functions, methods, that returnother functions or take functions as a parameter. In the next chapterwe’ll see that a lot of design principles in object-oriented program‐ming can be simplified by the adoption of functional concepts likehigher-order functions. Then we’ll look at how many of the behavio‐ral design patterns are actually doing a similar job to higher-orderfunctions.

Summary | 5

Page 16: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers
Page 17: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

CHAPTER 2

SOLID Principles

Lambda-Enabled SOLID PrinciplesThe SOLID principles are a set of basic principles for designing OOprograms. The name itself is an acronym, with each of the five prin‐ciples named after one of the letters: Single responsibility, Open/closed, Liskov substitution, Interface segregation, and Dependencyinversion. The principles act as a set of guidelines to help you imple‐ment code that is easy to maintain and extend over time.

Each of the principles corresponds to a set of potential code smellsthat can exist in your code, and they offer a route out of the prob‐lems caused. Many books have been written on this topic, and I’mnot going to cover the principles in comprehensive detail.

In the case of all these object-oriented principles, I’ve tried to find aconceptually related approach from the functional-programmingrealm. The goal here is to both show functional and object-orientedprogramming are related, and also what object-oriented program‐mers can learn from a functional style.

The Single-Responsibility PrincipleEvery class or method in your program should have only a single rea‐son to change.

An inevitable fact of software development is that requirementschange over time. Whether because a new feature needs to be added,your understanding of your problem domain or customer has

7

Page 18: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

changed, or you need your application to be faster, over time soft‐ware must evolve.

When the requirements of your software change, the responsibilitiesof the classes and methods that implement these requirements alsochange. If you have a class that has more than one responsibility,when a responsibility changes, the resulting code changes can affectthe other responsibilities that the class possesses. This possiblyintroduces bugs and also impedes the ability of the code base toevolve.

Let’s consider a simple example program that generates a BalanceSheet. The program needs to tabulate the BalanceSheet from a listof assets and render the BalanceSheet to a PDF report. If the imple‐menter chose to put both the responsibilities of tabulation and ren‐dering into one class, then that class would have two reasons forchange. You might wish to change the rendering in order to gener‐ate an alternative output, such as HTML. You might also wish tochange the level of detail in the BalanceSheet itself. This is a goodmotivation to decompose this problem at the high level into twoclasses: one to tabulate the BalanceSheet and one to render it.

The single-responsibility principle is stronger than that, though. Aclass should not just have a single responsibility: it should alsoencapsulate it. In other words, if I want to change the output format,then I should have to look at only the rendering class and not at thetabulation class.

This is part of the idea of a design exhibiting strong cohesion. A classis cohesive if its methods and fields should be treated togetherbecause they are closely related. If you tried to divide up a cohesiveclass, you would result in accidentally coupling the classes that youhave just created.

Now that you’re familiar with the single-responsibility principle, thequestion arises, what does this have to do with lambda expressions?Well lambda expressions make it a lot easier to implement thesingle-responsibility principle at the method level. Let’s take a lookat some code that counts the number of prime numbers up to a cer‐tain value (Example 2-1).

8 | Chapter 2: SOLID Principles

Page 19: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Example 2-1. Counting prime numbers with multiple responsibilitiesin a method

public long countPrimes(int upTo) { long tally = 0; for (int i = 1; i < upTo; i++) { boolean isPrime = true; for (int j = 2; j < i; j++) { if (i % j == 0) { isPrime = false; } } if (isPrime) { tally++; } } return tally;}

It’s pretty obvious that we’re really doing two different responsibili‐ties in Example 2-1: we’re counting numbers with a certain property,and we’re checking whether a number is a prime. As shown inExample 2-2, we can easily refactor this to split apart these tworesponsibilities.

Example 2-2. Counting prime numbers after refactoring out theisPrime check

public long countPrimes(int upTo) { long tally = 0; for (int i = 1; i < upTo; i++) { if (isPrime(i)) { tally++; } } return tally;}

private boolean isPrime(int number) { for (int i = 2; i < number; i++) { if (number % i == 0) { return false; } } return true;}

Unfortunately, we’re still left in a situation where our code has tworesponsibilities. For the most part, our code here is dealing with

The Single-Responsibility Principle | 9

Page 20: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

looping over numbers. If we follow the single-responsibility princi‐ple, then iteration should be encapsulated elsewhere. There’s also agood practical reason to improve this code. If we want to count thenumber of primes for a very large upTo value, then we want to beable to perform this operation in parallel. That’s right—the thread‐ing model is a responsibility of the code!

We can refactor our code to use the Java 8 streams library (seeExample 2-3), which delegates the responsibility for controlling theloop to the library itself. Here we use the range method to count thenumbers between 0 and upTo, filter them to check that they reallyare prime, and then count the result.

Example 2-3. Counting primes using the Java 8 streams API

public long countPrimes(int upTo) { return IntStream.range(1, upTo) .filter(this::isPrime) .count();}

private boolean isPrime(int number) { return IntStream.range(2, number) .allMatch(x -> (number % x) != 0);}

So, we can use higher-order functions in order to help us easilyimplement the single-responsibility principle.

The Open/Closed PrincipleSoftware entities should be open for extension,

but closed for modification.—Bertrand Meyer

The overarching goal of the open/closed principle is similar to thatof the single-responsibility principle: to make your software lessbrittle to change. Again, the problem is that a single feature requestor change to your software can ripple through the code base in away that is likely to introduce new bugs. The open/closed principleis an effort to avoid that problem by ensuring that existing classescan be extended without their internal implementation being modi‐fied.

10 | Chapter 2: SOLID Principles

Page 21: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

When you first hear about the open/closed principle, it sounds like abit of a pipe dream. How can you extend the functionality of a classwithout having to change its implementation? The actual answer isthat you rely on an abstraction and can plug in new functionalitythat fits into this abstraction. We can also use higher-order functionsand immutability to achieve similar aims in a functional style.

AbstractionRobert Martin’s interpretation of the open/closed principle was thatit was all about using polymorphism to easily depend upon anabstraction. Let’s think through a concrete example. We’re writing asoftware program that measures information about system perfor‐mance and graphs the results of these measurements. For example,we might have a graph that plots how much time the computerspends in user space, kernel space, and performing I/O. I’ll call theclass that has the responsibility for displaying these metricsMetricDataGraph.

One way of designing the MetricDataGraph class would be to haveeach of the new metric points pushed into it from the agent thatgathers the data. So, its public API would look something likeExample 2-4.

Example 2-4. The MetricDataGraph public API

class MetricDataGraph {

public void updateUserTime(int value);

public void updateSystemTime(int value);

public void updateIoTime(int value);

}

But this would mean that every time we wanted to add in a new setof time points to the plot, we would have to modify the MetricDataGraph class. We can resolve this issue by introducing an abstraction,which I’ll call a TimeSeries, that represents a series of points intime. Now our MetricDataGraph API can be simplified to notdepend upon the different types of metric that it needs to display, asshown in Example 2-5.

The Open/Closed Principle | 11

Page 22: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Example 2-5. Simplified MetricDataGraph API

class MetricDataGraph {

public void addTimeSeries(TimeSeries values);

}

Each set of metric data can then implement the TimeSeries inter‐face and be plugged in. For example, we might have concrete classescalled UserTimeSeries, SystemTimeSeries, and IoTimeSeries. Ifwe wanted to add, say, the amount of CPU time that gets stolen froma machine if it’s virtualized, then we would add a new implementa‐tion of TimeSeries called StealTimeSeries. MetricDataGraph hasbeen extended but hasn’t been modified.

Higher-Order FunctionsHigher-order functions also exhibit the same property of being openfor extension, despite being closed for modification. A good exam‐ple of this is the ThreadLocal class. The ThreadLocal class providesa variable that is special in the sense that each thread has a singlecopy for it to interact with. Its static withInitial method is ahigher-order function that takes a lambda expression that representsa factory for producing an initial value.

This implements the open/closed principle because we can get newbehavior out of ThreadLocal without modifying it. We pass in a dif‐ferent factory method to withInitial and get an instance ofThreadLocal with different behavior. For example, we can useThreadLocal to produce a DateFormatter that is thread-safe withthe code in Example 2-6.

Example 2-6. A ThreadLocal date formatter

// One implementationThreadLocal<DateFormat> localFormatter = ThreadLocal.withInitial(SimpleDateFormat::new);

// UsageDateFormat formatter = localFormatter.get();

12 | Chapter 2: SOLID Principles

Page 23: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

We can also generate completely different behavior by passing in adifferent lambda expression. For example, in Example 2-7 we’re cre‐ating a unique identifier for each Java thread that is sequential.

Example 2-7. A ThreadLocal identifier

// Or...AtomicInteger threadId = new AtomicInteger();ThreadLocal<Integer> localId = ThreadLocal.withInitial(() -> threadId.getAndIncrement());

// Usageint idForThisThread = localId.get();

ImmutabilityAnother interpretation of the open/closed principle that doesn’t fol‐low in the object-oriented vein is the idea that immutable objectsimplement the open/closed principle. An immutable object is onethat can’t be modified after it is created.

The term “immutability” can have two potential interpretations:observable immutability or implementation immutability. Observableimmutability means that from the perspective of any other object, aclass is immutable; implementation immutability means that theobject never mutates. Implementation immutability implies observ‐able immutability, but the inverse isn’t necessarily true.

A good example of a class that proclaims its immutability butactually is only observably immutable is java.lang.String, as itcaches the hash code that it computes the first time its hashCodemethod is called. This is entirely safe from the perspective of otherclasses because there’s no way for them to observe the differencebetween it being computed in the constructor every time or cached.

I mention immutable objects in the context of this report becausethey are a fairly familiar concept within functional programming.They naturally fit into the style of programming that I’m talkingabout.

Immutable objects implement the open/closed principle in the sensethat because their internal state can’t be modified, it’s safe to add newmethods to them. The new methods can’t alter the internal state ofthe object, so they are closed for modification, but they are addingbehavior, so they are open to extension. Of course, you still need to

The Open/Closed Principle | 13

Page 24: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

be careful in order to avoid modifying state elsewhere in your pro‐gram.

Immutable objects are also of particular interest because they areinherently thread-safe. There is no internal state to mutate, so theycan be shared between different threads.

If we reflect on these different approaches, it’s pretty clear that we’vediverged quite a bit from the traditional open/closed principle. Infact, when Bertrand Meyer first introduced the principle, he definedit so that the class itself couldn’t ever be altered after being comple‐ted. Within a modern Agile developer environment, it’s pretty clearthat the idea of a class being complete is fairly outmoded. Businessrequirements and usage of the application may dictate that a class beused for something that it wasn’t intended to be used for. That’s nota reason to ignore the open/closed principle though, just a goodexample of how these principles should be taken as guidelines andheuristics rather than followed religiously or to the extreme. Weshouldn’t judge the original definition too harshly, however, since itused in a different era and for software with specific and definedrequirements.

A final point that I think is worth reflecting on is that in the contextof Java 8, interpreting the open/closed principle as advocating anabstraction that we can plug multiple classes into or advocatinghigher-order functions amounts to the same approach. Because ourabstraction needs to be represented by an interface upon whichmethods are called, this approach to the open/closed principle isreally just a usage of polymorphism.

In Java 8, any lambda expression that gets passed into a higher-orderfunction is represented by a functional interface. The higher-orderfunction calls its single method, which leads to different behaviordepending upon which lambda expression gets passed in. Again,under the hood, we’re using polymorphism in order to implementthe open/closed principle.

The Liskov Substitution PrincipleLet q(x) be a property provable about objects x of type T. Then q(y)should be true for objects y of type S where S is a subtype of T.

The Liskov substitution principle is often stated in these very formalterms, but is actually a very simple concept. Informally we can think

14 | Chapter 2: SOLID Principles

Page 25: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

of this as meaning that child classes should maintain the behaviorthey inherit from their parents. We can split out that property intofour distinct areas:

• Preconditions cannot be strengthened in a subtype. Where theparent worked, the child should.

• Postconditions cannot be weakened in a subtype. Where theparent caused an effect, then the child should.

• Invariants of the supertype must be preserved in a subtype.Where parent always stuck left or maintained something, thenthe child should as well.

• Rule from history: don’t allow state changes that your parentdidn’t. For example, a mutable point can’t subclass an immuta‐ble point.

Functional programming tends to take a different perspective toLSP. In functional programming inheritance of behavior isn’t a keytrait. If you avoid inheritance hierachies then you avoid the prob‐lems that are associated with them, which is the antipattern that theLiskov substitution principle is designed to solve. This is actuallybecoming increasing accepted within the object-oriented commu‐nity as well through the composite reuse principle: compose, don’tinherit.

The Interface-Segregation PrincipleThe dependency of one class to another one should depend on thesmallest possible interface

In order to properly understand the interface-segregation principle,let’s consider a worked example in which we have people who workin a factory during the day and go home in the evening. We mightdefine our worker interface as follows:

Example 2-8. Parsing the headings out of a file

interface Worker { public void goHome(); public void work(); }

Initially our AssemblyLine requires two types of Worker: anAssemblyWorker and a Manager. Both of these go home in the eve‐

The Interface-Segregation Principle | 15

Page 26: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

ning but have different implementations of their work methoddepending upon what they do.

As time passes, however, and the factory modernizes, they start tointroduce robots. Our robots also do work in the factory, but theydon’t go home at the end of the day. We can see now that our workerinterface isn’t meeting the ISP, since the goHome() method isn’t reallypart of the minimal interface.

Now the interesting point about this example is that it all relates tosubtyping. Most statically typed object-oriented languages, such asJava and C++, have what’s known as nominal subtyping. This meansthat for a class called Foo to extend a class called Bar, you need to seeFoo extends Bar in your code. The relationship is explicit andbased upon the name of the class. This applies equally to interfacesas well as classes. In our worked example, we have code likeExample 2-9 in order to let our compiler know what the relationshipis between classes.

Example 2-9. Parsing the headings out of a file

class AssemblyWorker implements Worker class Manager implements Worker class Robot implements Worker

When the compiler comes to check whether a parameter argumentis type checked, it can identify the parent type, Worker, and checkbased upon these explicit named relationships. This is shown inExample 2-10.

Example 2-10. Parsing the headings out of a file

public void addWorker(Worker worker) { workers.add(worker); }

public static AssemblyLine newLine() { AssemblyLine line = new AssemblyLine(); line.addWorker(new Manager()); line.addWorker(new AssemblyWorker()); line.addWorker(new Robot()); return line; }

16 | Chapter 2: SOLID Principles

Page 27: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

The alternative approach is called structural subtyping, and here therelationship is implicit between types based on the shape/structureof the type. So if you call a method called getFoo() on a variable,then that variable just needs a getFoo method; it doesn’t need toimplement an interface or extend another class. You see this a lot infunctional programming languages and also in systems like the C++template framework. The duck typing in languages like Ruby andPython is a dynamically typed variant of structural subtyping.

If we think about this hypothetical example in a language which usesstructural subtyping, then our example might be re-written likeExample 2-11. The key is that the parameter worker has no explicittype, and the StructuralWorker implementation doesn’t need to sayexplicitly that it implements or extends anything.

Example 2-11. Parsing the headings out of a file

class StructuralWorker { def work(step:ProductionStep) { println("I'm working on: " + step.getName) } }

def addWorker(worker) { workers += worker }

def newLine() = { val line = new AssemblyLine line.addWorker(new Manager()) line.addWorker(new StructuralWorker()) line.addWorker(new Robot()) line }

Structural subtyping removes the need for the interface-segregationprinciple, since it removes the explicit nature of these interfaces. Aminimal interface is automatically inferred by the compiler from theuse of these parameters.

The Dependency-Inversion PrincipleAbstractions should not depend on details; details should depend onabstractions.

The Dependency-Inversion Principle | 17

Page 28: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

On of the ways in which we can make rigid and fragile programsthat are resistant to change is by coupling high-level business logicand low-level code designed to glue modules together. This isbecause these are two different concerns that may change over time.

The goal of the dependency-inversion principle is to allow program‐mers to write high-level business logic that is independent of low-level glue code. This allows us to reuse the high-level code in a waythat is abstract of the details upon which it depends. This modular‐ity and reuse goes both ways: we can substitute in different details inorder to reuse the high-level code, and we can reuse the implemen‐tation details by layering alternative business logic on top.

Let’s look at a concrete example of how the dependency-inversionprinciple is traditionally used by thinking through the high-leveldecomposition involved in implementing an application that buildsup an address book automatically. Our application takes in asequence of electronic business cards as input and accumulates ouraddress book in some storage mechanism.

It’s fairly obvious that we can separate this code into three basicmodules:

• The business card reader that understands an electronic busi‐ness card format

• The address book storage that stores data into a text file• The accumulation module that takes useful information from

the business cards and puts it into the address book

We can visualize the relationship between these modules as shownin Figure 2-1.

Figure 2-1. Dependencies

In this system, while reuse of the accumulation model is more com‐plex, the business card reader and the address book storage do not

18 | Chapter 2: SOLID Principles

Page 29: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

depend on any other components. We can therefore easily reusethem in another system. We can also change them; for example, wemight want to use a different reader, such as reading from people’sTwitter profiles; or we might want to store our address book insomething other than a text file, such as a database.

In order to give ourselves the flexibility to change these componentswithin our system, we need to ensure that the implementation ofour accumulation module doesn’t depend upon the specific detailsof either the business card reader or the address book storage. So, weintroduce an abstraction for reading information and an abstractionfor writing information. The implementation of our accumulationmodule depends upon these abstractions. We can pass in the specificdetails of these implementations at runtime. This is the dependency-inversion principle at work.

In the context of lambda expressions, many of the higher-orderfunctions that we’ve encountered enable a dependency inversion. Afunction such as map allows us to reuse code for the general conceptof transforming a stream of values between different specific trans‐formations. The map function doesn’t depend upon the details of anyof these specific transformations, but upon an abstraction. In thiscase, the abstraction is the functional interface Function.

A more complex example of dependency inversion is resource man‐agement. Obviously, there are lots of resources that can be managed,such as database connections, thread pools, files, and network con‐nections. I’ll use files as an example because they are a relativelysimple resource, but the principle can easily be applied to morecomplex resources within your application.

Let’s look at some code that extracts headings from a hypotheticalmarkup language where each heading is designated by being suf‐fixed with a colon (:). Our method is going to extract the headingsfrom a file by reading the file, looking at each of the lines in turn,filtering out the headings, and then closing the file. We shall alsowrap any Exception related to the file I/O into a friendly domainexception called a HeadingLookupException. The code looks likeExample 2-12.

The Dependency-Inversion Principle | 19

Page 30: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Example 2-12. Parsing the headings out of a file

public List<String> findHeadings(Reader input) { try (BufferedReader reader = new BufferedReader(input)) { return reader.lines() .filter(line -> line.endsWith(":")) .map(line -> line.substring(0, line.length() - 1)) .collect(toList()); } catch (IOException e) { throw new HeadingLookupException(e); }}

Unfortunately, our heading-finding code is coupled with theresource-management and file-handling code. What we really wantto do is write some code that finds the headings and delegates thedetails of a file to another method. We can use a Stream<String> asthe abstraction we want to depend upon rather than a file. A Streamis much safer and less open to abuse. We also want to be able to apass in a function that creates our domain exception if there’s aproblem with the file. This approach, shown in Example 2-13,allows us to segregate the domain-level error handling from theresource-management-level error handling.

Example 2-13. The domain logic with file handling split out

public List<String> findHeadings(Reader input) { return withLinesOf( input, lines -> lines.filter(line -> line.endsWith(":")) .map(line -> line.substring(0, line.length()-1)) .collect(toList()), HeadingLookupException::new);}

I expect that you’re now wondering what that withLinesOf methodlooks like! It’s shown in Example 2-14.

Example 2-14. The definition of withLinesOf

private <T> T withLinesOf( Reader input, Function<Stream<String>, T> handler, Function<IOException, RuntimeException> error) { try (BufferedReader reader = new BufferedReader(input)) {

20 | Chapter 2: SOLID Principles

Page 31: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

return handler.apply(reader.lines()); } catch (IOException e) { throw error.apply(e); }}

withLinesOf takes in a reader that handles the underlying file I/O.This is wrapped up in BufferedReader, which lets us read the fileline by line. The handler function represents the body of whatevercode we want to use with this function. It takes the Stream of thefile’s lines as its argument. We also take another handler called errorthat gets called when there’s an exception in the I/O code. This con‐structs whatever domain exception we want. This exception thengets thrown in the event of a problem.

To summarize, higher-order functions provide an inversion of con‐trol, which is a form of dependency-inversion. We can easily usethem with lambda expressions. The other observation to note withthe dependency-inversion principle is that the abstraction that wedepend upon doesn’t have to be an interface. Here we’ve reliedupon the existing Stream as an abstraction over raw reader and filehandling. This approach also fits into the way that resource manage‐ment is performed in functional languages—usually a higher-orderfunction manages the resource and takes a callback function that isapplied to an open resource, which is closed afterward. In fact, iflambda expressions had been available at the time, it’s arguable thatthe try-with-resources feature of Java 7 could have been imple‐mented with a single library function.

SummaryWe’ve now reached the end of this section on SOLID, but I think it’sworth going over a brief recap of the relationships that we’veexposed. We talked about how the single-responsibility principlemeans that classes should only have a single reason to change. We’vetalked about how we can use functional-programming ideas to ach‐ieve that end, for example, by decoupling the threading model fromapplication logic. The open/closed principle is normally interpretedas a call to use polymorphism to allow classes to be written in amore flexible way. We’ve talked about how immutability and higher-order functions are both functional programming techniques whichexhibit this same open/closed dynamic.

Summary | 21

Page 32: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

The Liskov substitution principle imposes a set of constraintsaround subclassing that defines what it means to implement a cor‐rect subclass. In functional programming, we de-emphasize inheri‐tance in our programming style. No inheritance, no problem! Theinterface-segregation principle encourages us to minimize thedependency on large interfaces that have multiple responsibilities.By moving to functional languages that encourage structural sub‐typing, we remove the need to declare these interfaces.

Finally we talked about how higher-order functions were really aform of dependency inversion. In all cases, the SOLID principlesoffer us a way of writing effective object-oriented programs, but wecan also think in functional terms and see what the equivalentapproach would be in that style.

22 | Chapter 2: SOLID Principles

Page 33: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

CHAPTER 3

Design Patterns

Functional Design PatternsOne of the other bastions of design we’re all familiar with is the ideaof design patterns. Patterns document reusable templates that solvecommon problems in software architecture. If you spot a problemand you’re familiar with an appropriate pattern, then you can takethe pattern and apply it to your situation. In a sense, patterns codifywhat people consider to be a best-practice approach to a given prob‐lem.

In this section, we’re instead going to look at how existing designpatterns have become better, simpler, or in some cases, implementa‐ble in a different way. In all cases, the application of lambda expres‐sions and a more functional approach are the driving factor behindthe pattern changing.

The Command PatternA command object is an object that encapsulates all the informationrequired to call another method later. The command pattern is a wayof using this object in order to write generic code that sequences andexecutes methods based on runtime decisions. There are four classesthat take part in the command pattern, as shown in Figure 3-1:

23

Page 34: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

ReceiverPerforms the actual work

CommandEncapsulates all the information required to call the receiver

InvokerControls the sequencing and execution of one or more com‐mands

ClientCreates concrete command instances

Figure 3-1. The command pattern

Let’s look at a concrete example of the command pattern and seehow it improves with lambda expressions. Suppose we have a GUIEditor component that has actions upon it that we’ll be calling, suchas open or save, like in Example 3-1. We want to implement macrofunctionality—that is, a series of operations that can be recordedand then run later as a single operation. This is our receiver.

Example 3-1. Common functions a text editor may have

interface Editor {

void save();

void open();

void close();

24 | Chapter 3: Design Patterns

Page 35: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

}

In this example, each of the operations, such as open and save, arecommands. We need a generic command interface to fit these differ‐ent operations into. I’ll call this interface Action, as it representsperforming a single action within our domain. This is the interfacethat all our command objects implement (Example 3-2).

Example 3-2. All our actions implement the Action interface

interface Action {

void perform();

}

We can now implement our Action interface for each of the opera‐tions. All these classes need to do is call a single method on ourEditor and wrap this call into our Action interface. I’ll name theclasses after the operations that they wrap, with the appropriate classnaming convention—so, the save method corresponds to a classcalled Save. Example 3-3 and Example 3-4 are our commandobjects.

Example 3-3. Our save action delegates to the underlying method callon Editor

class Save implements Action {

private final Editor editor;

public Save(Editor editor) { this.editor = editor; }

@Override public void perform() { editor.save(); }}

The Command Pattern | 25

Page 36: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Example 3-4. Our open action also delegates to the underlying methodcall on Editor

class Open implements Action {

private final Editor editor;

public Open(Editor editor) { this.editor = editor; }

@Override public void perform() { editor.open(); }}

Now we can implement our Macro class. This class can recordactions and run them as a group. We use a List to store thesequence of actions and then call forEach in order to execute eachAction in turn. Example 3-5 is our invoker.

Example 3-5. A macro consists of a sequence of actions that can beinvoked in turn

class Macro {

private final List<Action> actions;

public Macro() { actions = new ArrayList<>(); }

public void record(Action action) { actions.add(action); }

public void run() { actions.forEach(Action::perform); }

}

When we come to build up a macro programmatically, we add aninstance of each command that has been recorded to the Macroobject. We can then just run the macro, and it will call each of thecommands in turn. As a lazy programmer, I love the ability to definecommon workflows as macros. Did I say “lazy?” I meant focused on

26 | Chapter 3: Design Patterns

Page 37: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

improving my productivity. The Macro object is our client code andis shown in Example 3-6.

Example 3-6. Building up a macro with the command pattern

Macro macro = new Macro();macro.record(new Open(editor));macro.record(new Save(editor));macro.record(new Close(editor));macro.run();

How do lambda expressions help? Actually, all our commandclasses, such as Save and Open, are really just lambda expressionswanting to get out of their shells. They are blocks of behavior thatwe’re creating classes in order to pass around. This whole patternbecomes a lot simpler with lambda expressions because we canentirely dispense with these classes. Example 3-7 shows how to useour Macro class without these command classes and with lambdaexpressions instead.

Example 3-7. Using lambda expressions to build up a macro

Macro macro = new Macro();macro.record(() -> editor.open());macro.record(() -> editor.save());macro.record(() -> editor.close());macro.run();

In fact, we can do this even better by recognizing that each of theselambda expressions is performing a single method call. So, we canactually use method references in order to wire the editor’s com‐mands to the macro object (see Example 3-8).

Example 3-8. Using method references to build up a macro

Macro macro = new Macro();macro.record(editor::open);macro.record(editor::save);macro.record(editor::close);macro.run();

The command pattern is really just a poor man’s lambda expressionto begin with. By using actual lambda expressions or method refer‐

The Command Pattern | 27

Page 38: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

ences, we can clean up the code, reducing the amount of boilerplaterequired and making the intent of the code more obvious.

Macros are just one example of how we can use the command pat‐tern. It’s frequently used in implementing component-based GUIsystems, undo functions, thread pools, transactions, and wizards.

There is already a functional interface with the samestructure as our interface Action in core Java—Runnable. We could have chosen to use that in ourmacro class, but in this case, it seemed more appropri‐ate to consider an Action to be part of the vocabularyof our domain and create our own interface.

Strategy PatternThe strategy pattern is a way of changing the algorithmic behaviorof software based upon a runtime decision. How you implement thestrategy pattern depends upon your circumstances, but in all cases,the main idea is to be able to define a common problem that issolved by different algorithms and then encapsulate all the algo‐rithms behind the same programming interface.

An example algorithm we might want to encapsulate is compressingfiles. We’ll give our users the choice of compressing our files usingeither the zip algorithm or the gzip algorithm and implement ageneric Compressor class that can compress using either algorithm.

First we need to define the API for our strategy (see Figure 3-2),which I’ll call CompressionStrategy. Each of our compression algo‐rithms will implement this interface. They have the compressmethod, which takes and returns an OutputStream. The returnedOutputStream is a compressed version of the input (seeExample 3-9).

28 | Chapter 3: Design Patterns

Page 39: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

Figure 3-2. The Strategy Pattern

Example 3-9. Defining a strategy interface for compressing data

interface CompressionStrategy {

OutputStream compress(OutputStream data) throws IOException;

}

We have two concrete implementations of this interface, one forgzip and one for zip, which use the built-in Java classes to writegzip (Example 3-10) and zip (Example 3-11) files.

Example 3-10. Using the gzip algorithm to compress data

class GzipCompressionStrategy implements CompressionStrategy {

@Override public OutputStream compress(OutputStream data) throws IOException { return new GZIPOutputStream(data); }

}

Example 3-11. Using the zip algorithm to compress data

class ZipCompressionStrategy implements CompressionStrategy {

@Override public OutputStream compress(OutputStream data) throws IOException { return new ZipOutputStream(data);

Strategy Pattern | 29

Page 40: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

}

}

Now we can implement our Compressor class, which is the contextin which we use our strategy. This has a compress method on it thattakes input and output files and writes a compressed version of theinput file to the output file. It takes the CompressionStrategy as aconstructor parameter that its calling code can use to make a run‐time choice as to which compression strategy to use—for example,getting user input that would make the decision (see Example 3-12).

Example 3-12. Our compressor is provided with a compression strategyat construction time

class Compressor {

private final CompressionStrategy strategy;

public Compressor(CompressionStrategy strategy) { this.strategy = strategy; }

public void compress(Path inFile, File outFile) throws IOException { try (OutputStream outStream = new FileOutputStream(outFile)) { Files.copy(inFile, strategy.compress(outStream)); } }}

If we have a traditional implementation of the strategy pattern, thenwe can write client code that creates a new Compressor withwhichever strategy we want (Example 3-13).

Example 3-13. Instantiating the Compressor using concrete strategyclasses

Compressor gzipCompressor = new Compressor(new GzipCompressionStrategy());gzipCompressor.compress(inFile, outFile);

Compressor zipCompressor = new Compressor(new ZipCompressionStrategy());zipCompressor.compress(inFile, outFile);

As with the command pattern discussed earlier, using either lambdaexpressions or method references allows us to remove a whole layer

30 | Chapter 3: Design Patterns

Page 41: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

of boilerplate code from this pattern. In this case, we can removeeach of the concrete strategy implementations and refer to a methodthat implements the algorithm. Here the algorithms are representedby the constructors of the relevant OutputStream implementation.We can totally dispense with the GzipCompressionStrategy and ZipCompressionStrategy classes when taking this approach.Example 3-14 is what the code would look like if we used methodreferences.

Example 3-14. Instantiating the Compressor using method references

Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);gzipCompressor.compress(inFile, outFile); Compressor zipCompressor = new Compressor(ZipOutputStream::new);zipCompressor.compress(inFile, outFile);

Yet again thinking in a more functional way—modelling in terms offunctions rather than classes and objects—has allowed us to reducethe boilerplate and simplify an existing design pattern. This is thegreat win about being able to combine the functional and object-oriented world view: you get to pick the right approach for the rightsituation.

SummaryIn this section, we have evaluated a series of design patterns andtalked about how they could all be used differently with lambdaexpressions. In some respect, a lot of these patterns are really object-oriented embodiments of functional ideas. Take the command pat‐tern. It’s called a pattern and has some different interacting compo‐nents, but the essence of the pattern is the passing around andinvoking of behavior. The command pattern is all about first-classfunctions.

The same thing with the Strategy pattern. Its really all about puttingtogether some behavior and passing it around; again it’s a designpattern that’s mimicking first-class functions. Programming lan‐guages that have a first-class representation of functions often don’ttalk about the strategy or command patterns, but this is what devel‐opers are doing. This is an important theme in this report. Oftentimes, both functional and object-oriented programming languages

Summary | 31

Page 42: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

end up with similar patterns of code, but with different names asso‐ciated with them.

32 | Chapter 3: Design Patterns

Page 43: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

CHAPTER 4

Conclusions

Object-Oriented vs. Functional LanguagesIn this report, we’ve covered a lot of ways in which ideas from func‐tional programming relate to existing object-oriented design princi‐ples. These idioms aren’t as different as a lot of people make themout to be. Definitely functional programming emphasizes the powerof reuse and composition of behavior through higher-order func‐tions. And there’s no doubt that immutable data structures improvethe safety of our code. But these features can be supported in anobject-oriented context as well, the common theme being the bene‐fits that be achieved are universal to both approaches. We’re alwaysseeking to write code that is safer and offers more opportunity forcomposing together behavior in a flexible manner.

Functional programming is about a thought process. It’s not neces‐sarily the case that you need a new language in order to program ina functional style. Some language features often help though. Theintroduction of lambda expressions in Java 8 makes it a languagemore suited to functional programming. While object-oriented pro‐gramming traditionally has been about encapsulating data andbehavior together, it is now adding more support for behavior on itsown thanks to ideas from functional programming.

Other languages such as Scala or Haskell take functional ideas fur‐ther. Scala offers a mix of both functional and object-oriented pro‐gramming facilities, whilst Haskell focuses purely on functional pro‐gramming. It’s well worth exploring these languages and seeing what

33

Page 44: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

set of language features you find useful in your problem domain.However, there’s no need to necessarily move to Scala or Haskellthinking that they’re the only way to program in a functional style.However, they certainly offer some features that Java lacks, and it’ssometimes worth using different programming languages.

I appreciate that this maybe is a controversial opinion to those whohave spent their careers advocating functional programming, butsoftware development isn’t about idealism or religious evangelism:it’s about producing reliable software that works for our clients andbusiness. A functional style of programming can help us achieve thisend, but it is not the end in and of itself.

Programming Language EvolutionOne of the interesting trends over time in programming languagesis the gradual shift between languages that are more object-orientedand more functional. If we jump in our Delorean and go back intime to the 1980s, a lot of interesting changes were going on. Olderprocedural programming lanugages were being phased out andthere was a growth in the popularity of both object-oriented andfunctional programming languages.

Interestingly enough, a lot of the early advocates of both functionaland object-oriented languages combined features of the other. If youask any object-oriented purist what her ideal programming languageis, she’ll tell you it’s Smalltalk. Smalltalk 80 had lambda expressions,and Smalltalk’s collections library was inherently functional innature, and equivalent operations to map, reduce, and filter existed(albeit under different names).

A lot of purist functional programmers from the period would tellyou that Common LISP is the ideal functional language. Interest‐ingly enough, it had a system for object orientation called CLOS(Common LISP Object System). So back in the 1980s, there was rea‐sonable recognition that neither paradigm was the only true way toenlightenment.

During the 1990s, programming changed. Object-oriented pro‐gramming became established as a dominant programmingapproach for business users. Languages such as Java and C++ grewin popularity. In the late 1990s and early 2000s, Java became a

34 | Chapter 4: Conclusions

Page 45: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

hugely popular language. In 2001, the JavaOne conference had28,000 attendees. That’s the size of a rock concert!

At the time of writing, the trend has changed again. Popular pro‐gramming languages are moving away from being specificallyobject-oriented or functional. You will no doubt get the old holdoutsuch as Haskell or Clojure, but by and large, languages are goinghybrid. Both C++ and Java 8 have added lambda expressions andstarted to retrofit useful elements of functional programming totheir existing object-oriented capabilities. Not only that but theunderlying ideas which have been adopted in terms of generics inJava and templating in C++ originated in functional programminglanguages.

Newer languages are multiparadigm from the get-go. F# is a greatexample of a language which has a functional style but also main‐tains parity with C# in terms of its object-oriented features. Lan‐guages such as Ruby, Python, and Groovy can be written in both afunctional and object-oriented style, all having functional features intheir collections API. There are a number of newer languages on theJVM that have developed over the last decade or so, and they pre‐dominantly have a mixture of functional and object-oriented fea‐tures. Scala, Ceylon, and Kotlin all come to mind in this regard.

The future is hybrid: pick the best features and ideas from bothfunctional and object-oriented approaches in order to solve theproblem at hand.

Programming Language Evolution | 35

Page 46: Object-Oriented vs. Functional Programming · that programming is basically just math, and that now is the time for everyone to think in terms of functions. Object-oriented programmers

About the AuthorRichard Warburton is an empirical technologist, solver of deep-dive technical problems, and works independently as a softwareengineer and trainer. Recently he wrote Java 8 Lambdas (O’Reilly)and helps developers learn via Iteratr Learning and Pluralsight. He’sworked as a developer in diverse areas, including statistical analyt‐ics, static analysis, compilers, and network protocols. He is a leaderin the London Java community. Richard is also a known conferencespeaker, having talked at Devoxx, JavaOne, QCon SF, JFokus,Devoxx UK, Geecon, Oredev, JAX London, JEEConf, and Codemo‐tion. He obtained a PhD in computer science from The Universityof Warwick.