Experiencing functional programming in Java
In the recent days I’ve been working on my Functional Programming skills in Java.
Reading about it and implementing small functions makes it easy to understand the basics. But it’s always better to have hands-on experience.
So I gave myself the task to implement a service which creates a simple search index for this blog.
The idea
The core idea is to realize the following flow:
As you can see, this is already a pipe which can be realized through the chaining of functions:
preprocess
.andThen(tokenize)
.andThen(addSynonyms)
.andThen(addToIndex)
.apply(page);
The aggregate
The entrance to the ‘indexer’ is an aggregate which is in turn an own Function:
public class IndexAggregate implements Function<Page, DocIndex> {}
As the IndexAggregate provides one entry point for the outside, the sub-parts are not interesting for the outside.
The aggregate encapsulates this functions into one function and one big task: Indexing a page. And as this is the core business of the index service, it should do the thing well.
In the first draft of the IndexAggregate I just created the interfaces of the functions and composed them together:
public class TokenizerAggregate implements Function<Page, DocIndex> {
Function<Page, Page> preprocess;
Function<Page, TokenizedPage> tokenize;
Function<TokenizedPage, SynonymizedPage> addSynonyms;
Function<SynonymizedPage, DocIndex> addToIndex;
@Override
public DocIndex apply(Page p) {
return preprocess
.andThen(tokenize)
.andThen(addSynonyms)
.andThen(addToIndex)
.apply(p);
}
}
This class shows that the declarative way (a characteristic of FP) is pretty compact. Furthermore it is easy and fast to create a first draft of the core functionality!
After this definition, the Functions could be implemented by them self. They have their own domain logic, boundary and task to fulfill. But they don’t have the knowledge of the other parts. So they are loosely coupled and don’t call each other or share any objects/references. The only thing they know is which input they get and which output they need to provide.
Divide and conquer
This division of the problem is a huge win. For sure, this is a simple, greenfield project and one man show. So I can drive the development for myself. But there was no need to think about side effects, as every function had its own task to do.
… don’t over-engineer
But you can also use this to over-engineer things.
It’s not always the best way to split everything into really small parts.
For example the following functional decomposition of a simple write to a JSON-file with GSON is a little overkill:
public class JsonFileWriter {
private final Gson gson = new Gson();
private final Function<String, FileWriter> open = (path) -> {
try {
return new FileWriter(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
private final BiFunction<IndexEntry[], FileWriter, FileWriter> save = (entry, fw) -> {
gson.toJson(entry, fw);
return fw;
};
private final Consumer<FileWriter> close = fw -> {
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
private final BiFunction<IndexEntry[], String, FileWriter> openAndSave = (entry, path) -> save.apply(entry, open.apply(path));
public BiConsumer<IndexEntry[], String> store = (entry, path) -> close.accept(
openAndSave.apply(entry, path)
);
}
Nevertheless it was a fun way to split it up. In OOP you could just create one write method and that’s it.
The concept of ‘small functions with one task and no side-effects’ probably can be sometimes too much. But you know, over-engineering can be done in different ways.
Conclusion
A small project that is data-driven can help to apply functional programming style. Practice is good and gives a better understanding of the declarative way of thinking.
I still think this is a skill which will make me benefit even in the OOP world, as Java uses a mix of both styles! Functional Programming in Java is real and exists there. Who would expect something other as it exists there since at least Java 8!