Lambdas are not functional programming

John McClean
9 min readNov 21, 2018

No-one in Java-land is doing functional programming, and that is a good thing.

Just because you are using Lambda expressions, does not mean you are doing functional programming.

Lambda expressions in Java are simply a less verbose way of creating (slightly constrained) Objects and as such the most likely outcome of adopting Lambda’s without a good understanding of core functional concepts is gnarly, twisted, hard to follow, obfuscated imperative Object Oriented code with a nice concise syntax. Yes, we could write exactly the same dodgy code in a more verbose way by creating individual classes and lambdas.

Conversely, even when writing actual functional code in Java we could write the same code with verbose Class definitions and Objects as we can with concise lambdas.

Seek help

For those that disagree passionately with my first sentence, and are busy searching for the (strangely hard to find) reply button on Medium (“well actually.. my team have been doing fp in Java“) — ⛔️stop!⛔️. If for some crazy reason you are doing functional programming in Java, instead of reading my rants you probably should have a good long hard think about your language choices.

Just what the hell is functional programming then?

A lot of people in Java-land are trying to mix traditional imperative Java code and functional-ish code, with varying degrees of success. Java is not a functional language, it is fundamentally an Object Oriented language that allows us to adopt some functional concepts in so far as we enforce their correct implementation through developer discipline. Unlike in Haskell, Idris, Ocaml (and even Scala) the compiler alone won’t save us.

If we are going adopt functional principles to mix Object Oriented and Functional Programming successfully in Java, we had better know what they are. What are the core features of functional languages?

Is it laziness?

Streams in Java are lazy (although Optional and CompletableFuture aren’t). Perhaps laziness is a core tenet of fp, and to be truly functional we also must be lazy. Haskell is lazy language, but unfortunately Ocaml, Idris and ML (to name but 3) are strict (eager) by default. I don’t think anyone would argue that Idris is not a functional language.

Laziness and performance

Laziness can improve performance, after all the most performant code is code not executed. In the simple example below we will stop processing once we have loaded 10 good records from somewhere (rather than load all records first and then filter them).

Stream.generate(this::loadFromSomewhere)
.filter(this::identifyStuffWeWant)
.limit(10)
.collect(Collectors.toList());

Whether this is a real performance benefit or not is debatable, we can always couple our filtering and limit logic with our loading logic in some manner even when processing the data eagerly to avoid doing excess work (we are arguably getting cleaner code not better performance with laziness). The Haskell wiki has this to say on laziness and performance

Laziness can be a useful tool for improving performance, but more often than not it reduces performance by adding a constant overhead to everything. Because of laziness, the compiler can’t evaluate a function argument and pass the value to the function, it has to record the expression in the heap in a suspension (or thunk) in case it is evaluated later. Storing and evaluating suspensions is costly, and unnecessary if the expression was going to be evaluated anyway.

And since at least 2015 it’s been possible to configure Haskell to use strict evaluation by default.

Laziness is a really cool feature of functional languages that we can leverage to our advantage in Java, but I don’t believe it is one of the critical traits of functional languages from which their primary benefits derive.

Is it functional composition?

This is definitely a big part of it. All of the functional languages previously mentioned have functions as first class citizens and once we have that we can chain function calls together by matching the types.

E.g. from A functional approach to dependency injection in Java we can compose a function that accepts a DAO and returns a String with another that accepts a String and returns a String by matching the output with the first, with the input of the second.

public Function<DAO,String> loadName(long id);loadName(10l).andThen(name->"User's name is "+ name);Function<DAO,String> sentence = loadName(10l).andThen(name->"User's name is "+ name);System.out.println(sentence.apply(dao));

The ability to compose functions like this, opens the door to higher order functions and by extension to implementations of concepts / patterns from category theory like Monads and Functors.

But all of this only works if the functions are pure -> that is that they are not mutating or impacting state outside of the function. Which brings us to the next potential core trait.

Is it immutability?

This is also massively important for functional programming. All of the aforementioned languages encourage immutability by default and eschew (to varying degrees) mutability altogether. In the Java ecosystem most of our core datastructures (in the JDK itself) are mutable, and there is limited native support for creating and using immutable objects.

Alvesgaspar [GFDL (http://www.gnu.org/copyleft/fdl.html) or CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons

Hoverflies mimic the colouring of wasps to ward of predators. If you are mixing Lambda’s with mutable state, you are creating imperative hoverflies masquerading as functional code. The conciseness of the syntax may well hide implementations that are simply more complex than the typical OO alternatives and miss all of the advantages of their FP analogs.

So yes, immutability matters a lot. In fact in Java this is one of the first places I would start (here are two primers : Dysfunctional programming in Java 2 : Immutability and Dysfunctional programming in Java 7 : Immutable Collections).

Is it the type system?

Grappling with a more powerful type system that enforces that functions are pure is perhaps one the biggest hurdles OO developers have to jump in making the leap to functional programming. Scala, OCaml, ML, Haskell and Idris vary in the power and strictness of their type systems — all offer a type system much more powerful than Javas. The advanced functional languages like Haskell and Idris not only have extremely advanced type systems, but the compiler enforcement of correctness is highly intolerant.

By contrast Java, with it’s weaker type system, also has a highly tolerant compiler (if we ask it nicely, it will allow us to compile just about anything 🙊).

We can cast classes almost at will (the code below compiles).

Object o = 10;
String str =(String)o;

We can mutate external (& even global state) anywhere.

Function<String,Integer> fn = a -> { 
State.set(a);
return a.length();
}

We can extend existing class hierarchies and introduce new sub-types the original developers were entirely unaware of and may not account for in their business logic.

if(animal instanceof Dog){}else if(animal instanceof Cat){}else if(animal instanceof Lion){}public class MyDinosaur extends Animal { //oops!}

We can even create new interface implementations via dynamic proxies and new sub-class definitions at runtime (with cglib), and more completely mimic the machinations of dynamic languages (like Ruby, Groovy or Python — all of which have closures, none of which are functional), which can lead to the hair pulling joy of Exceptions like this :-

org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm

We can use the ServiceLoader (and other mechanisms) to completely change runtime behaviour of our applications based on the jars present. Got two implementations of a particular provider in your test environment (as you have your compile time dependencies and your test deps), but only one in production — then your application may behave entirely differently in prod than in your unit tests. Good luck with debugging that!

We can throw CheckedExceptions even on methods that don’t declare them (via Exception softening).

public void doIO(){
throw uncheck(new IOException("not declared!!");
}
private static <T extends Throwable> T uncheck(final Throwable throwable) throws T {
throw (T) throwable;
}

We can write code like this and the compiler won’t complain :-

List<String> createList(){
List strs = new ArrayList();
strs.add(“hello”);
strs.add(“hello”);
strs.add(10);
return strs;
}

By contrast functional languages often have extremely intolerant compilers that take away a lot of our favourite Java hacks. We’ve spent decades figuring out ways to bypass the (limited) Java type system to behave more like a dynamically typed language than a powerfully statically typed one. Most of our popular core libraries rely heavily on these hacks (good luck trying to avoid them!). These restricted functional languages are much more difficult to work with (at least at first) as a result, but also much more reliable once you get used to them.

Is it monads and pro-functor optics?

The introduction of concepts from category theory into typed functional programming helps us work with the compiler enforced constraints. They can offer some very nice patterns for programming, but it is those constraints that liberate us from the tyranny of (often very hard to debug) runtime errors. I’ve spent much of the last week slowly identifying a jar conflict that only manifests at runtime in Java — removing one dependency at a time, stepping deeply through complex 3rd party libraries.

It is compile time correctness

With their crazy type systems, restrictions on mutable state, fetishisation of pure functions the functional languages help pry us away from the bad habits that lead us to runtime errors. Instead the compiler kindly let’s us know that we can’t really flatMap / bind a WriterT[Option[String]] with a WriterT[Either[Error,String]] and expect it to work (or even better that trying to throw new Exception, return null or mutate that input parameter just isn’t going to cut it either).

That way, we pay the cost of figuring out much of the right way to code whatever it is we are working on, up front, at the start when we are in the office during normal work hours, on our own workstations without impacting our collegues. And certainly not at 3am having brought down production because we (or a library we are using) made a illegal reflective call to a method someone had changed since the last time someone else tested it.

If Javac won’t help us, what can we do?

If you are looking to truly adopt the advantages of functional programming in Java and not just create mind bendingly gnarly imperative code with Lambda’s then I believe we need to work with the Java Compiler to help it help us.

Rather than spend our time trying to trick it, we should :-

Make good use of Generic Types. Generics in Java can trace their orgins to Generic Java created by Philip Wadler (Haskell legend), Martin Odersky (creator of Scala), Gilad Bracha, Dave Stoutamire. Don’t ignore type parameters, declare them and enforce them everywhere. Minimize casting and if instanceOfing. Where you do use them (it is still Java after all — they are likely impossible to fully eliminate), centralize each type of cast within a resuable method, use proper type parameters on the inputs and outputs and write good tests!

Make illegal states unrepresentable in our code. The Haskell and Idris compilers will enforce this for you, just pretend Javac is as strict. Avoid nulls, don’t throw Exceptions, avoid locking and synchronization and you will take giant leaps towards this.

Make our own data classes immutable and final where possible, use immutable collections (proper ones). Avoid instanceof checks where possible, and where you use them make sure the types are not extensible.

Use libraries that avoid runtime magic and reflection where pragmatically possible.

OO and FP together

There is no conflict between OO and FP.

You can be Object Oriented and still adopt the core constraints that drive the benefits of the functional paradigm. You can make sensible use of an immutable, strongly and correctly typed domain model with good inheritance / composition trade offs and choices made, with methods that act as pure functions, avoid global state entirely & isolate the IO portions of your application (and you’ll be happier and sleep better as a result!).

The real tension: imperative and fp

In Java you will rely on developer discipline to enforce these liberating constraints. But even as you do so within your own applications, unless and until the eco-system adopts this principles whole heartedly, you will have to make heavy use of libraries and frameworks which do not. The real tension isn’t between OO and FP, it’s between mutual imperative code that by passes where possible compiler checks to maximise runtime dynamism (as if all we needed was to migrate to Ruby) and constrained functional code that enables the compiler to find problems in our code before we do.

--

--

John McClean

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie