Functional Programming (FP) may be confusing and difficult to understand, especially for developers who have a lot of experience in Object-Oriented Programming (OOP).
And it is really challenge because you have to learn a lot of new concepts and functional data structures.
I keep going through all this concepts and functional data structures after two years of learning and practice FP. But the most challenge for me was how to start codding in FP style. Another words, how to code functionally.
Of course, you have to learn basics and concepts of FP. But all this knowledge are difficult to use in practice at the first time. Because you have to switch your way of thinking and start to think functionally.
And today, I tell how to think functionally at software development.
Functional way of thinking
To be honest, all people think functionally… before they start codding.
What does it mean? So, look on example. Wife call husband and ask to buy a cake. What does she tell? Let’s look on first conversation:
Husband:
- Hi, honey.
Wife:
- Hello, darling. I would like a cake to dinner. So listen my instructions. First, go out from office and go to the west 3150 meters up to crossroad. Then turn on the left and go forward 273 meters up to MyFavorite bakery. Come in the bakery and if there is a line then stand at the end of it. Wait while you are not in the head of the line. After that buy my favorite cake.
Husband:
- Roger!
Compare previous talk with another one.
Husband:
- Hi, honey.
Wife:
- Hi, darling. Could you buy a cake in MyFavorite backery after work?
Husband:
- Sure honey!
So, what conversation is closer to real life? I think the most of you choose the last one. And I completely agree with you.
But if you are OOP developer and you have to code such task, I think most of you implement something like that:
public Bakery bakery = new Bakery();
public HusbandWithCake buyCake(Husband husband) {
husband.getLocation().setX(husband.getLocation().getX() + 3150);
husband.getLocation().setY(husband.getLocation().getY() + 273);
bakery.addVisitor(husband);
while (bakery.peekLineHead() != husband) { Thread.sleep(5); }
var husbandWithCake = bakery.buy(Bakery.CAKE);
bakery.leave(husbandWithCake);
return husbandWithCake;
}
Such solution is depict the first version of conversation. But in real life you suppose to implement the last one. Strange, is not it?
So, and how does this example relate to functional way of thinking?
Simple. Please, re-read the second conversation and take into account:
There is no implementation details. Just ask someone to do something. And that is functional way of thinking - ask someone to do something. Implementation details are not your concern if you are not doing exactly what are you asked for.
“What” instead of “How”
Just ask someone to do something. It means you tell what need to do but do not provide how exactly to do that.
Such approach is called Declarative programming. SQL is one of the most famous example of declarative programming paradigm. And functional programming is declarative too. Please, take a look on following functional styled code snippet:
public String getInsuranceName(Person person) {
return Optional
.ofNullable(person)
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
In the most cases, each code line in FP should descride “what” to do and hide “how” it will be done. Compare code snippet above with this one:
public String getInsuranceName(Person person) {
if (person == null || // how
person.getCar() == null ||
person.getCar().getInsurance() == null) {
return "Unknown";
}
return person.getCar().getInsurance().getName(); // what
}
Where “how” lives in FP
You may be confusing about hiding implementation details. But hiding does not mean absence of implementation details. Of course, you have to code implementation somewhere. And before I uncover this “somewhere” I must tell about one important thing related to functions.
Generally in FP, you should strictly split your functions into 2 groups:
- Lambdas (how)
- Higher-order functions (what)
Lambdas
Usually lambda has following structure:
( input arguments ) arrow sign { lambda expression }
So, lambda expression contains implementation details. And usually code in lambda expression is written at imperative way that is used as common code style in OOP.
You can think about lambda like as a worker function* that performs some unit of work.
* All kinds of lambda could be devided into three groups:
1. Function ( x ) -> { y }
2. Consumer ( x ) -> {}
3. Supplier () -> { y }In this article words “lambda” and “function” are used interchangeably in order to simplify examples.
And worker function has unified interface ( x ) -> { y }. It means if you put some value “x” as input into function that function returns some value “y” as its work result. And function’s implementation usually is hidden from function’s user behind of interface (x) -> { y }. Such approach give us apportunity to choose implementation that suites the best at particular place and time.
Let’s return to our second conversation example. Husband is worker function in this example. And wife as programmer provides information what need to do (value “x”), but she does not provide how husband should achive that. According to function interface, she expects that husband brings a cake (value “y”) to dinner. And worker function could implement any algorithm to achive that.
For example, husband could get taxi to MyFavorite bakery or maybe take order to delivery food from MyFavorite bakery. All this examples move us to the next level: higher-order functions.
Higher-order functions
A higher-order function is a function that takes other functions as input arguments and returns function.
Let’s look on example with taxi. Taxi is higher-order function in this example. It gets function “husband” as input argument and return new function “taxi with husband”. Execution on this function results in moving husband from office to MyFavorite bakery. Taxi driver is worker function that performs work.
Examples in this article are simple models of real life. And of course, real life is more complicated and has numerious input parameters, variables and results. But simple models help us understand complicated things.
And usually higher-order functions focus on “what” need to do. “Drive to” is what taxi does. And taxi driver as worker function defines how to get to target place.
Computation pipelines
So, worker function as unit of work contains implementation details and higher-order function is used to chain multiply worker functions into serial actions. Such sequence of actions are called computation pipeline.
So, husband could build computation pipeline like this:
- Get a taxi.
- Go to MyFavorite bakery.
- Buy a cake.
- Come back home.
And it is functional way of thinking:
Tell “what” need to do instead of “how”.
FP application structure overview
This point is out of article scope but I have mentioned a lot about function (lambda), higher-order function and computation pipeline. And I think I have to place here their hierarhy and relations between them because FP is based on functions whereas OOP base on classes. Therefore, application structure in FP quite different rather than in OOP.

This hierarhy is quite simplified and does not depict different kinds of lambda, functional data structures, combinators and etc.
Practice
Please, read code snippets below and ask “what” question to each code line. And then try to write down in notepad what code exactly do.
Code’s programming language does not matter. Try to read code as human, not as programmer. It is power of functional programming to write human readable code.
All code snippets are from real applications that I have developed.
Code snippet 1:
public interface BusinessPartnerPersistencePipelines<T extends BusinessPartner> extends Supplier<BusinessPartnerPersistenceSteps<T>> {
default Function<T, T> createPartnerFor(Long marketId) {
var steps = this.get();
return steps
.createPartnerObject(marketId)
.andThen(steps.persistPartner())
.andThen(steps.findPartner())
.andThen(steps.logCreate());
}
default Function<T, T> updatePartner() {
var steps = this.get();
return steps
.persistPartner()
.andThen(steps.findPartner())
.andThen(steps.logUpdate());
}
default void deletePartnerBy(Long partnerId) {
var steps = this.get();
Optional.ofNullable(partnerId)
.map(steps.findPartnerById())
.ifPresent(steps.deletePartner());
}
// further code is omitted due to make code snippet concise
}
Code snippet 2:
public class TweeCache {
public <K, V> TweeCache putAll(String tableName, Map<K, V> values) {
return putAll(tableClass -> tableName, values);
}
private <K, V> TweeCache putAll(Function<Class<?>, String> toTableName, Map<K, V> values) {
Optional.ofNullable(values)
.map(Map::values)
.flatMap(mapValues ->
mapValues.stream().findFirst())
.map(Object::getClass)
.map(toTableName.andThen(toTableCache))
.ifPresent(cache -> cache.putAll(values));
return this;
}
// further code is omitted due to make code snippet concise
}
Code snippet 3:
public interface Statistics {
static Function<IssueChanges, IssueStats> collect() {
return ApplicativeFunctor.of(IssueStats::concat,
Statistics.Issues.all(),
Statistics.Changes.all());
}
interface Issues {
static Function<IssueChanges, IssueStats> all() {
return ApplicativeFunctor.of(IssueStats::concat,
created(),
resolved(),
id());
}
static Function<IssueChanges, IssueStats> created() {
return issueChanges -> Optional
.ofNullable(issueChanges)
.flatMap(fieldValue("created"))
.map(date ->
new IssueStats(singletonMap(StatisticName.CREATED, date)))
.orElseGet(IssueStats::empty);
}
static Function<IssueChanges, Optional<String>> fieldValue(String name) {
return issueChanges -> Optional
.ofNullable(issueChanges)
.map(IssueChanges::getIssue)
.map(issue -> issue.field(name))
.map(Issue.Field::value);
}
// further code is omitted due to make code snippet concise
}
// further code is omitted due to make code snippet concise
}
Check yourself
Here is the same code snippets as above but now they are contains comments what each line do. Go ahead and check yourself.
Code snippet 1:
public interface BusinessPartnerPersistencePipelines<T extends BusinessPartner> extends Supplier<BusinessPartnerPersistenceSteps<T>> {
default Function<T, T> createPartnerFor(Long marketId) {
var steps = this.get(); // get pipeline steps
return steps
.createPartnerObject(marketId) // create partner object with particular market id
.andThen(steps.persistPartner()) // persist partner object into persistence storage
.andThen(steps.findPartner()) // get persisted partner object from persistence storage
.andThen(steps.logCreate()); // log create action about newpartner
}
default Function<T, T> updatePartner() {
var steps = this.get(); // get pipeline steps
return steps
.persistPartner() // persist partner object into persistence storage
.andThen(steps.findPartner()) // get persisted partner object from persistence storage
.andThen(steps.logUpdate()); // log update action about particular partner
}
default void deletePartnerBy(Long partnerId) {
var steps = this.get(); // get pipeline steps
Optional.ofNullable(partnerId) // put partnerId variable into Optional context
.map(steps.findPartnerById()) // get persisted partner object from persistence storage
.ifPresent(steps.deletePartner()); // delete partner if he present in persistance storage
}
// further code is omitted due to make code snippet concise
}
Code snippet 2:
public class TweeCache {
public <K, V> TweeCache putAll(String tableName, Map<K, V> values) {
return putAll(tableClass -> tableName, values); // put all values into cache where table class transforms into table name
}
private <K, V> TweeCache putAll(Function<Class<?>, String> toTableName, Map<K, V> values) {
Optional.ofNullable(values) // put values into Optional context
.map(Map::values) // extract (map) values from Map data structure
.flatMap(mapValues -> // transform values into...
mapValues.stream().findFirst()) // ...first element of values
.map(Object::getClass) // Get object class
.map(toTableName.andThen(toTableCache)) // transform (map) object class to table name and then to table cache
.ifPresent(cache -> cache.putAll(values)); // put values to cache if it present
return this; // return this instance of class
}
// further code is omitted due to make code snippet concise
}
Code snippet 3:
public interface Statistics {
static Function<IssueChanges, IssueStats> collect() {
return ApplicativeFunctor.of(IssueStats::concat, // create functor with IssueStats concat function to apply
Statistics.Issues.all(), // apply functor for all Issue statistics
Statistics.Changes.all()); // apply functor for all Changes statistics
}
interface Issues {
static Function<IssueChanges, IssueStats> all() {
return ApplicativeFunctor.of(IssueStats::concat, // create functor with IssueStats concat function to apply
created(), // apply function to "created" statistic
resolved(), // apply function to "resolved" statistic
id()); // apply function to "id" statistic
}
static Function<IssueChanges, IssueStats> created() {
return issueChanges -> Optional
.ofNullable(issueChanges) // put issue changes into Optional context
.flatMap(fieldValue("created")) // extract "created" field's value
.map(date -> // transform date into instance of IssueStats class
new IssueStats(singletonMap(StatisticName.CREATED, date)))
.orElseGet(IssueStats::empty); // return empty IssueStats object if "created" field is not found
}
static Function<IssueChanges, Optional<String>> fieldValue(String name) {
return issueChanges -> Optional
.ofNullable(issueChanges) // put issue changes into Optional context
.map(IssueChanges::getIssue) // extract Issue from IssueChanges
.map(issue -> issue.field(name)) // extract field from Issue by name
.map(Issue.Field::value); // extract field's value
}
// further code is omitted due to make code snippet concise
}
// further code is omitted due to make code snippet concise
}