Functional Java by Example | Part 6 – Functions as Parameters
This is part 6 of the series called “Functional Java by Example”.
The example I’m evolving in each part of the series is some kind of “feed handler” which processes documents. In previous part we tried to make our functions as pure possible by moving as much of the side-effects, such as IO, to the outside of the system.
Now we’re going to replace some of our abstractions into functions, to be passed as parameters.
If you came for the first time, it’s best to start reading from the beginning. It helps to understand where we started and how we moved forward throughout the series.
These are all the parts:
- Part 1 – From Imperative to Declarative
- Part 2 – Tell a Story
- Part 3 – Don’t Use Exceptions to Control Flow
- Part 4 – Prefer Immutability
- Part 5 – Move I/O to the Outside
- Part 6 – Functions as Parameters
- Part 7 – Treat Failures as Data Too
- Part 8 – More Pure Functions
I will update the links as each article is published. If you are reading this article through content syndication please check the original articles on my blog.
Each time also the code is pushed to this GitHub project.
Collaborators OO-style
Remember how we left things previously?
class FeedHandler { Webservice webservice List<Doc> handle(List<Doc> changes) { changes .findAll { doc -> isImportant(doc) } .collect { doc -> createResource(doc) .thenApply { resource -> setToProcessed(doc, resource) } .exceptionally { e -> setToFailed(doc, e) } .get() } } private CompletableFuture<Resource> createResource(doc) { webservice.create(doc) } private static boolean isImportant(doc) { doc.type == 'important' } private static Doc setToProcessed(doc, resource) { doc.copyWith( status: 'processed', apiId: resource.id ) } private static Doc setToFailed(doc, e) { doc.copyWith( status: 'failed', error: e.message ) } }
Above feed handler needs a “web service” to do its work.
Take a look at the following part where a collaborator of type WebService
is used to create a resource based on a document:
class FeedHandler { Webservice webservice List<Doc> handle(List<Doc> changes) { changes .collect { doc -> createResource(doc) ... } private CompletableFuture<Resource> createResource(doc) { webservice.create(doc) } }
Remember, instead of just returning a resource directly, we’ve wrapped it in a CompletableFuture
as part of our exception handling mechanism.
What if we wanted something other than a WebService
to create a resource?
Well, this is where it gets tricky and easy at the same time — and where a OO-style can conflict a bit with a FP-style.
You see, WebService
is a Java interface and defined as follows:
interface Webservice { CompletableFuture<Resource> create(Doc doc) }
This follows the Dependency Inversion Principle (DIP) — as part of the SOLID design principles promoted by Robert C. Martin — which (amongst others) says:
Abstractions should not depend on details. Details should depend on abstractions.
WebService
is already an abstraction for any kind of webservice implementation. So the system could have multiple implementations of this interface e.g. a REST implementation and a SOAP implementation:
class RestWebService implements Webservice { @Override CompletableFuture<Resource> create(Doc doc) { // do REST communication } } class SoapWebService implements Webservice { @Override CompletableFuture<Resource> create(Doc doc) { // do SOAP communication } }
The feed handler does not care about the details — it just wants something which adheres to the contract defined by the WebService
interface: there’s a create
method which accepts a Doc
and returns a CompletableFuture
.
The FeedHandler
class has a property webservice
holding the reference to a WebService
. Any OO-developer recognizes this style, because it’s very familiar: all the collaborators are present in properties, which are (often) initialized during constructing.
As soon as FeedHandler
is constructed, it gets an instance of WebService
passed to it – albeit constructor-injection or property-injection, either through DI frameworks or plain-old manual labor.
For brevity I have been omitting the constructor in my code snippets, but as you can see in my testcases I definitely pass all dependencies using the constructor Groovy generates for me under the hood ��
Collaborators FP-style
Ok, if we would put on our Functional Hat again, we would need to revisit the way how a WebService
gets passed to the feed handler.
The handle
method’s signature does not mention anything other than: documents go in, and documents come out.
class FeedHandler { ... List<Doc> handle(List<Doc> changes) { ... } }
I can not assume the same output is returned for the same input — because the method secretly depends on something on the outside: the WebService
.
Well, possibly I control the entire creation of the feed handler, including the WebService
, but the reference to webservice
can change in between method invocations, yielding other results every time handle
is using it. Unless I made it immutable or prevent the reference from being updated. I told you it could get tricky
Can we make handle
pure, just as we did in previous installments with the isImportant
, setToProcessed
and setToFailed
methods?
In this case we have to pass WebService
in as a parameter, just as the list of documents.
We change
class FeedHandler { Webservice webservice List<Doc> handle(List<Doc> changes) { ... } }
into
class FeedHandler { List<Doc> handle(List<Doc> changes, Webservice webservice) { ... } }
At every invocation of handle
we pass in everything it needs: the documents it needs to handle and the webservice it needs to use.
Since this method no longer depends on any properties in the FeedHandler
class anymore, we could have made it static
at the moment — upgrading it to a class-level method.
Higher-order functions
Effectively our handle
method just became a so-called “higher order function”, a function that takes a function or returns a function.
So, back to a question I asked in the beginning: what if we wanted something other than a WebService
to create a resource?
It shouldn’t even have to be a webservice right? Maybe we completely want to go bananas and a have a monkey create a resource for us?
class Monkey implements Webservice { @Override CompletableFuture<Resource> create(Doc doc) { // go bananas! But do create resources plz } }
That just looks weird, doesn’t it? The WebService
interface is too specific for the abstraction feed handler needs. Anything which creates resources will do, doesn’t it?
A better name would be “ResourceCreator” — so just rename the interface.
Old:
interface Webservice { CompletableFuture<Resource> create(Doc doc) }
New:
interface ResourceCreator { CompletableFuture<Resource> create(Doc doc) }
A ResourceCreator
interface with a create
method; how fitting! Now anything can implement this interface, and feed handler doesn’t even care whether or not it is a webservice, a monkey or a Hobbit.
The new method signature:
class FeedHandler { List<Doc> handle(List<Doc> changes, ResourceCreator creator) { ... } }
Perfect abstraction!
Functional abstractions
In Java we call an interface with only one abstract method a functional interface. Our ResourceCreator
fits this description; it has a single, abstract method create
.
Java’s java.util.function package has numerous of those functional interfaces — and they each have a single, defined purpose:
Consumer
represents a function that accepts an argument and returns nothingSupplier
represents a function that accepts no arguments, just returns a resultFunction
represents a function that accepts one argument and returns a result- …and more
What this means is, that we don’t need to define a specific interface, such as ResourceCreator
, every time we need a function “to accept one argument and return a result” — Function
is already an interface we can leverage!
This is how Function
(simplified) in Java 8 looks like:
interface Function<T,R> { R apply(T t); }
And this is how ResourceCreator
looks like right now:
interface ResourceCreator { CompletableFuture<Resource> create(Doc doc) }
You see we can completely substitute our ResourceCreator
with a Function
if we:
- substitute
Doc
for typeR
- substitute
CompletableFuture
for typeT
- substitute calling
create
by the methodapply
We can erase the ResourceCreator
interface completely!
The new method signature will become:
class FeedHandler { List<Doc> handle(List<Doc> changes, Function<Doc, CompletableFuture<Resource>> creator) { ... } }
What have we achieved?
- We can pass any function to
handle
now which takes a singleDoc
and produces a singleCompletableFuture
— and that’s all the feed handler needs to work properly. - As you’ve probably noticed by now that Functional Programming deals a lot with functions. A function can take another function, or could return a function.
- As of Java 8 we’ve got a whole bunch of functional interfaces, ready to use. Every developer can work with them in a standardized way, so it’s best to see if they fit your use case and API and re-use them wherever possible. Every one of them have generic types (such as
T
andR
) which can be used by you to indicate what goes in and what comes out of a function.
The complete code now looks like this:
class FeedHandler { List<Doc> handle(List<Doc> changes, Function<Doc, CompletableFuture<Resource>> creator) { changes .findAll { doc -> isImportant(doc) } .collect { doc -> creator.apply(doc) .thenApply { resource -> setToProcessed(doc, resource) } .exceptionally { e -> setToFailed(doc, e) } .get() } } private static boolean isImportant(doc) { doc.type == 'important' } private static Doc setToProcessed(doc, resource) { doc.copyWith( status: 'processed', apiId: resource.id ) } private static Doc setToFailed(doc, e) { doc.copyWith( status: 'failed', error: e.message ) } }
That’s it for now! Next time, we’re going to treat failures a data.
If you have any comments or suggestions, I’d love to hear about them!
Published on Java Code Geeks with permission by Ted Vinke, partner at our JCG program. See the original article here: Functional Java by Example | Part 6 – Functions as Parameters Opinions expressed by Java Code Geeks contributors are their own. |