Why Functional Programming matters

Naveen Muguda
9 min readJun 15, 2019

After successfully adopting the functional programming style at my day job for about 2 years, I believe it warrants wider adoption. In this post, I enumerate the reasons for the same.

Software development is an inherently multiple-dimensional problem, It needs to address challenges across these dimensions. Some of them and their facets are listed below.

I will focus on some of these facets, and explain how the adoption of functional is beneficial on them.

Developers

Simplicity vs Familiarity

I practice Yoga occasionally and perform two contrasting asanas

  1. Eagle Pose

2. Standing Forward Bend Pose

for most beginners figuring out ’what’ to perform the pose of the eagle takes time, but they are able to perform this within a few minutes of understanding.

The standing forward bend pose, on the other hand, is easy to understand but hard to perform. For most people, it takes months, if not years of practice to perform it properly.

The word ‘simple’ is derived from the world simplex, which means one bend. The standing forward bend pose has few bends and is easy to understand when compared to the eagle pose, which has more ‘twists’. Simplicity is a factor of the pose and is global, while familiarity is contextual and depends on the practitioner.

The above chart is a visualization of the two poses for a typical beginner.

Similarly, of the three common programming models, functional programming is arguably, the simplest and also the least familiar to the mainstream developers.

Attention

A vast majority of people taking this attention test fail to observe a key detail.

This is because there are limits on how much information most of us can store in our short term memory, this is a well-understood concept in the field of psychology called “The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information

The limits of human attention have to be factored into software development.

Cohesion

As mentioned in this post on a minimalist view of software development. Cohesive modules are small, they have focus, and are independently understandable.

The key abstractions of Functional Programming are types and functions. The principles of functional programming help in making these abstractions cohesive as explained below.

Purity

A function will always return the same value as output for the same set of inputs. This constraint precludes any state change to any of the inputs during any execution of the function. Additionally, all “reads” that a function does are limited to those reachable from inputs. This facilitates local reasoning.

Referential Transparency

for every execution of the function in every context, the execution should have the exact same effect as replacing it by the value returned by the function. A corollary from this property is that a function produces no side effects.

Immutability/Programming with values

Functional Programming deals with values, and not places. I.E. large portions of your code are dealing with values that don’t change during the execution. i.e. race conditions are eliminated.

Composition

Consider the task of performing the calculation of 39865 / 17.

Attempt this 1) without pen and paper and then 2) with pen and paper. The second attempt is guaranteed to be a lot easier than the first. We inherently break down a problem into smaller ones and combine the results.

Composability spans multiple aspects of human cognition. For e.g. from the sentence “I live in Gubbalala”, you would guess that Gubbalala is a place, by understanding the rest of the sentence. Similarly, the sentences “I like Bananas” and “I like Apples” have common prefixes. If languages were not composable, we wouldn’t have sentences, just an infinite set of words, making communication impossible.

Mapping this to software development, the best(and possibly) the only way of solving problems is by breaking them down into smaller problems and combining the solutions.

This style of decomposing a problem into smaller problems and combining them is fundamental to functional programming and manifests in many ways.

Function Composition

Consider the code snippet below which calculates (x + y) ^ 2

final BinaryOperator<Integer> binaryOperator = (x, y) -> x + y;
final BiFunction<Integer, Integer, Integer> sumOfSquares = binaryOperator.andThen(z -> z * z);
System.out.println(sumOfSquares.apply(2, 3));

It is composed of two parts, one that calculates the sum of the two inputs and another that squares the input. These two are “combined” to form a new function.

Higher-Order Functions/Functions as Values

public class MyList<E> {

private final List<E> _list;

public MyList(List<E> list) {
_list = list;
}

public E fold(BinaryOperator<E>binaryOperator, E zero) {
E result = zero;
for(E element:_list)
result = binaryOperator.apply(result, element);
return result;
}
}

consider the code snippet above and below

Function<MyList<Integer>, Integer> sum = (MyList<Integer> list) -> list.fold((x, y) -> x + y, 0);
Function<MyList<Integer>, Integer> product = (MyList<Integer> list) -> list.fold((x, y) -> x * y, 1);

We can see how functions were combined to create new (sum and product) functions.

Lifting of functions

consider the following two snippets

public <T> MyList<T> map(Function<E, T> function) {
final List<T> result = new ArrayList<>();
for(E element: _list)
result.add(function.apply(element));
return new MyList<>(result);
}

and

Function<MyList<Integer>, MyList<Integer>> listMod4 = list -> list.map(x -> x % 4);

A function(x -> x % 4) that was operating at the level of an individual integer was lifted and converted to a function that operates on a list of integers.

Essence over ceremony

https://imgs.xkcd.com/comics/dfs.png

This comic strip humorously depicts the perils of focusing on details.

The question “how do you make an egg salad sandwich” will typically elicit a 6–7 line answer. It will not include irrelevant details like buying the eggs, throwing the shells into the garbage bin, opening the mayonnaise can, etc. This is how humans think and communicate. We work with the essence, masking the ceremony.

Unfortunately, this elegant style of communication doesn’t always percolate to programming, leading to code that is difficult to read, test and maintain. like the example below from Java’s Integer class.

public static int bitCount(int i) {
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}

A recommended pattern to solve this issue in the programming world is the composed method pattern.

with the mechanisms of 1,)functions as values, 2)higher-order functions, and 3)lifting, the functional programming style is a progression of the composed method

Side effects of Side effects

Consider the code snippet below, it is a pseudo-code representation of preparing an egg salad.

class EggSaladRecipe {List<Egg> eggs;
List<BoiledEgg> boiledEggs;
List<PeeledEgg> peeledEggs;
boil(){
boiledEggs = ...
}
peel() {
peeledEggs = ...
}
}

the boil and peel methods have side effects. while it is possible to prepare an egg salad

recipe.boil();
recipe.peel();

the following code is also valid

recipe.peel();
recipe.boil();

notice how code involving side effects doesn’t prevent illegal combinations, which can lead to subtle bugs, which often surface at the worst possible time.

peel(boil(eggs))

if the code was written in a functional style, with referential transparency then the illegal state combinations are eliminated/reduced.

Side effects as values

What good is a program if it can’t mutate the state? Functional programming addresses this by separating the safe and unsafe parts. The safe parts don’t perform side effects but describe them. For e.g. “an intent to create file X” is created, instead of creating file X by the safe part. The intent is a value, which can be accumulated with other intended side effects.

The intent is passed to unsafe parts which are then executed. The intents are machine-readable and can be tested, and interpreted(possibly in different ways)

Effects

Humans are not good at following simple instructions consistently, that is why we need alarms and reminders such as the one below.

This behavior is prevalent in developers as well. There are always null pointer checks, error handling, validations that get missed.

Consider this example

String unit = person.getAddress().getCity().getStreet().getUnit();

A person’s address might be null, city information might be missing and so on. Relying on the developer to have considered all combinations of scenarios and code accordingly would be akin to having all drivers wear helmets or seat belts at the relevant times.

If it were to wrapped in a container such as Optional as shown below, then the safety is provided by the container, lessening the dependence on developer stamina and discipline.

String unit= person.flatMap(Person::getAddress)
.flatMap(Address::getCity)
.flatmap(City::getStreet)
.map(Street::getUnit)
.orElse("UNKNOWN");

Structure preserving operations

In the code snippet shown above, an Optional<Person> was transformed to Optional<Address> to Optional<Street> and so on. In each step, the resulting container was of the same kind(Optional) and it retained the effect(null safety). This behavior is prevalent across methods such as map, flatMap, and filter. This again reduces the reliance on human effort or intelligence.

Shared Vocabulary

How would you ask for a spoon 1) without using the word ‘spoon’ and 2)without describing what it is used for and 3)by describing its physical properties.

I bet it is not an easy exercise. Shared Vocabulary is key to “high bandwidth” communication.

Functional Programming provides a rich set of abstractions like functors, monads, monoids, and lenses. These facilitate “high bandwidth” communication and “high throughput” thinking.

Error Handling

The real world has plenty of things that go wrong. Scenarios like people forgetting passwords, requests timing out, wrong inputs being entered happen.

Railway Oriented Programming

Separation of concerns is a fundamental design principle. One of the many places where this is exercised in the Functional Programming style is the Railway Oriented Programming paradigm. It clearly separates the happy and unhappy paths, reducing the cognitive load on developers.

Time

Powerful abstractions

One of the promised and yet under-accomplished goals of software development is reuse. Most software development seems like building a car with legos. While it is possible, the resultant car will be costlier and less performant. The same is true for software built without reuse.

The composability described earlier, shortens the development time as well as guarantees/increases reliability as the individual pieces are well tested.

Open/Closed Principle

the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behavior to be extended without modifying its source code

Functional programming promotes behavioral parameterization, resulting in a lot of small functions. Given the well-defined and scoped behavioral boundaries, functions tend to get closed earlier than other modules like classes and methods.

Maintenance Cost

Programs written the functional way, tend to be shorter, and bigger fractions of them tend to be closed. This reduces the maintenance cost, compared to programs developed with other styles.

Business

Domain Focus

Compared to practices like Technology-Driven development, or Resume-Driven development, Clean Architecture puts emphasis on Domain. The heart of the software is its domain, which should be reasoned independently of any technology choices and nuances.

The interpretation style of functional programming fits like a glove to this principle. Technological choices are plugged into the domain, not vice versa.

Functional Domain Modelling

Domain models were of two types historically 1)Anemic Domain Model and 2)Rich domain Model.

Functional Domain Modelling is the functional programming take of the same. It is “leaner” and has composability baked into it.

Data Modelling

Modeling of data is a fundamental aspect of software development. Often we have to encode our knowledge into a not-so-expressive type system. The use of sum types prevents a powerful yet succinct way of modeling types. A good explanation of the same can be found here.

Infrastructure:

A surprisingly big fraction of problems involves working with values.

Computational jobs such as those running on Hadoop/Spark, Analytics, Business Warehousing involve computing with values. These jobs when written the functional way can get the advantages mentioned above and more

Immutability

Allows safe sharing of data between threads, which can be exploited to improve performance.

Parallelizability

the separation of concerns achieved with functional programming facilitates some optimizations.

one example is the parallelizability of fold. Note that this percolates to composed functions such as sum and product.

Escape Analysis facilitated optimizations

since functions don’t mutate fields, any mutations if any is to local variables. This behavior can be harnessed by compilers to perform storage-related optimizations

--

--