Functional programming for Java: getting started with Javaslang
Java is an old language and there are many new kids in the block who are challenging it on its own terrain (the JVM). However Java 8 arrived and brought a couple of interesting features. Those interesting features enabled the possibility of writing new amazing frameworks like the Spark web framework or Javaslang.
In this post we take a look at Javaslang which brings functional programming to Java.
Functional programming: what is that good for?
It seems that all the cool developers want to do some functional programming nowadays. As they wanted to use Object-oriented programming before. I personally think functional programming is great to tackle a certain set of problems, while other paradigms are better in other cases.
Functional programming is great when:
- you can pair it with immutability: a pure function has not side-effect and it is easier to reason about. Pure functions means immutability, which drastically simplifies testing and debugging. However not all solutions are nicely represent with immutability. Sometimes you just have a huge piece of data that it is shared between several users and you want to change it in place. Mutability is the way to go in that case.
- you have code which depends on inputs, not on state: if something depends on state instead than on input it sounds more like a method that a function to me. Functional code ideally should make very explicit which information is using (so it should use just parameters). That also means more generic and reusable functions.
- you have independent logic, which is not highly coupled: functional code is great when it is organized in small, generic and reusable functions
- you have streams of data that you want to transform: this is in my opinion the easiest place where you can see the values of functional programming. Indeed streams received a lot of attention in Java 8.
Discuss the library
As you can read on javaslang.com:
Java 8 introduced λc our programs, but “Clearly, the JDK APIs won’t help you to write concise functional logic (…)” – jOOQ™ blog
Javaslang™ is the missing part and the best solution to write comprehensive functional Java 8+ programs.
This is exactly as I see Javaslang: Java 8 gave us the enabling features to build more concise and composable code. But it did not do the last step. It opened a space and Javaslang arrived to fill it.
Javaslang brings to the table many features:
- currying: currying is the partial application of functions
- pattern matching: let’s think of it as the dynamic dispatching for functional programming
- failure handling: because exceptions are bad for function compositions
- Either: this is another structure which is very common in functional programming. The typical example is a function which returns a value when things go well and an error message when things go not so well
- tuples: tuples are a nice lightweight alternatives to objects and perfect to return multiple values. Just do not be lazy and use classes when it makes sense to do so
- memoization: this is caching for functions
For developers with experience in functional programming this will all sound very well known. For the rest of us let’s take a look at how we can use this stuff in practice.
Ok, but in practice how can we use this stuff?
Obviously showing an example for each of the feature of Javaslang is far beyond the scope of this post. Let’s just see how we could use some of them and in particular let’s focus on the bread and butter of functional programming: functions manipulation.
Given that I am obsessed with manipulation of Java code we are going to see how we can use Javaslang to examine the Abstract Syntax Tree (AST) of some Java code. The AST can be easily obtained using the beloved JavaParser.
If you are using gradle your build.gradle file could look like this:
apply plugin: 'java' apply plugin: 'idea' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile "com.javaslang:javaslang:2.0.0-beta" compile "com.github.javaparser:javaparser-core:2.3.0" testCompile "junit:junit:4.12" }
We are going to implement very simple queries. Queries we can be answered just looking at the AST without solving symbols. If you want to play with Java ASTs and solve symbols you may want to take a look at this project of mine: java-symbol-solver.
For example:
- find classes with a method with a given name
- find classes with a method with a given number of parameters
- find classes with a given name
- combining the previos queries
Let’s start with a function which given a CompilationUnit and a method name returns a List of TypeDeclarations defining a method with that name. For people who never used JavaParser: a CompilationUnit represents an entire Java file, possibly containing several TypeDeclarations. A TypeDeclaration can be a class, an interface, an enum or an annotation declaration.
import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.TypeDeclaration; import javaslang.Function1; import javaslang.Function2; import javaslang.collection.List; ... /** * Helper method */ public static boolean hasMethodNamed(TypeDeclaration typeDeclaration, String methodName) { return List.ofAll(typeDeclaration.getMembers()) .map(Match.whenType(MethodDeclaration.class) .then((t)-> Option.of(t.getName())).otherwise(() -> Option.none())) .map((n)->n.isDefined() && n.get().equals(methodName)) .reduce((a, b)->a || b); } public static List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) { return List.ofAll(cu.getTypes()).filter((t) -> hasMethodNamed(t, methodName)); }
getTypesWithThisMethod is very simple: we take all the types in the CompilationUnit (cu.getTypes()) and we filter them, selecting only the types which have a method with that name. The real work is done in hasMethodNamed.
In hasMethodNamed we start by creating a javaslang.collection.List from our java.util.List (List.ofAll(typeDeclaration.getMembers()). Then we consider that we are only interested in the MethodDeclarations: we are not interested in field declarations or other stuff contained in the type declaration. So we map each method declaration to either Option.of(true) if the name of the method matches the desidered methodName, otherwise we map it toOption.of(false). Everything that is not a MethodDeclaration is mapped to Option.none().
So for example, if we are looking for a method name “foo” in a class which has three fields, followed by methods named “bar”, “foo” and “baz” we will get a list of:
Option.none(), Option.none(), Option.none(), Option.of(false), Option.of(true), Option.of(false).
The next step is to map both Option.none() and Option.of(false) to false and Option.of(true) to true. Note that we could have than that immediately instead of having two maps operation concatenated. However I prefer to do things in steps. Once we get a list of true and false we need to derive one single value out of it, which should be true if the list contains at least one true, and false otherwise. Obtaining a single value from a list is called a reduce operation. There are different variants of this kind of operation: I will let you look into the details :)
We could rewrite the latest method like this:
public List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) { Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed; Function2<String, TypeDeclaration, Boolean> originalFunctionReversed = originalFunction.reversed(); Function1<String, Function1<TypeDeclaration, Boolean>> originalFunctionReversedAndCurried = originalFunction.reversed().curried(); Function1<TypeDeclaration, Boolean> originalFunctionReversedAndCurriedAndAppliedToMethodName = originalFunction.reversed().curried().apply(methodName); return List.ofAll(cu.getTypes()).filter(asPredicate( originalFunctionReversedAndCurriedAndAppliedToMethodName)); }
Why we would like to do so? It seems (and it is) much more complicate but it shows us how we can manipulate functions and this is an intermediate step to obtain code which is more flexible and powerful. So let’s try to understand what we are doing.
First a quick note: the class Function1 indicates a function taking one parameter. The first generic parameter is the type of the parameter accepted by the function, while the second one is the type of the value returned by the function. Function2 takes instead 2 parameters. You can understand how this goes on :)
We:
- reverse the order in which parameters can be passed to a function
- we create a partially applied function: this is a function in which the first parameter is “fixed”
So we create our originalFunctionReversedAndCurriedAndAppliedToMethodName just manipulating the original function hasMethodNamed. The original function took 2 parameters: a TypeDeclaration and the name of the method. Our elaborated function takes just a TypeDeclaration. It still returns a boolean.
We then simply transform our function in a predicate with this tiny function which we could reuse over and over:
private static <T> Predicate<T> asPredicate(Function1<T, Boolean> function) { return v -> function.apply(v); }
Now, this is how we can make it more generic:
/** * Get all the types in a CompilationUnit which satisfies the given condition */ public List<TypeDeclaration> getTypes(CompilationUnit cu, Function1<TypeDeclaration, Boolean> condition) { return List.ofAll(cu.getTypes()).filter(asPredicate(condition)); } /** * It returns a function which tells has if a given TypeDeclaration has a method with a given name. */ public Function1<TypeDeclaration, Boolean> hasMethodWithName(String methodName) { Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed; return originalFunction.reversed().curried().apply(methodName); } /** * We could combine previous function to get this one and solve our original question. */ public List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) { return getTypes(cu, hasMethodWithName(methodName)); }
Ok, now we could generalize also hasMethodWithName:
/** * This function returns true if the TypeDeclaration has at * least one method satisfying the given condition. */ public static boolean hasAtLeastOneMethodThat( TypeDeclaration typeDeclaration, Function1<MethodDeclaration, Boolean> condition) { return List.ofAll(typeDeclaration.getMembers()) .map(Match.whenType(MethodDeclaration.class) .then(m -> condition.apply(m)).otherwise(false)) .reduce((a, b)->a || b); } /** * We refactor this function to reuse hasAtLeastOneMethodThat */ public static boolean hasMethodWithName(TypeDeclaration typeDeclaration, String methodName) { return hasAtLeastOneMethodThat(typeDeclaration, m -> m.getName().equals(methodName)); }
After some refactoring we get this code:
package me.tomassetti.javaast; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.TypeDeclaration; import javaslang.Function1; import javaslang.Function2; import javaslang.collection.List; import javaslang.control.Match; import java.util.function.Predicate; public class AstExplorer { public static boolean hasAtLeastOneMethodThat( TypeDeclaration typeDeclaration, Function1<MethodDeclaration, Boolean> condition) { return hasAtLeastOneMethodThat(condition).apply(typeDeclaration); } public static Function1<TypeDeclaration, Boolean> hasAtLeastOneMethodThat( Function1<MethodDeclaration, Boolean> condition) { return t -> List.ofAll(t.getMembers()) .map(Match.whenType(MethodDeclaration.class) .then(m -> condition.apply(m)).otherwise(false)) .reduce((a, b)-> a || b); } public static boolean hasMethodNamed(TypeDeclaration typeDeclaration, String methodName) { return hasAtLeastOneMethodThat(typeDeclaration, m -> m.getName().equals(methodName)); } private static <T> Predicate<T> asPredicate(Function1<T, Boolean> function) { return v -> function.apply(v); } public static List<TypeDeclaration> typesThat( CompilationUnit cu, Function1<TypeDeclaration, Boolean> condition) { return List.ofAll(cu.getTypes()).filter(asPredicate(condition)); } public static Function1<TypeDeclaration, Boolean> methodHasName(String methodName) { Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed; return originalFunction.reversed().curried().apply(methodName); } public static List<TypeDeclaration> typesWithThisMethod(CompilationUnit cu, String methodName) { return typesThat(cu, methodHasName(methodName)); } }
Now let’s see how it can be used:
package me.tomassetti.javaast; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.TypeDeclaration; import javaslang.Function1; import javaslang.collection.List; import org.junit.Test; import java.io.InputStream; import static me.tomassetti.javaast.AstExplorer.*; import static org.junit.Assert.*; public class AstExplorerTest { @Test public void typesNamedA() throws ParseException { InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java"); CompilationUnit cu = JavaParser.parse(is); Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar"); List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(isNamedBar)); assertEquals(2, res.length()); assertEquals("A", res.get(0).getName()); assertEquals("B", res.get(1).getName()); } @Test public void typesHavingAMethodNamedBar() throws ParseException { InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java"); CompilationUnit cu = JavaParser.parse(is); Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar"); List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(isNamedBar)); assertEquals(2, res.length()); assertEquals("A", res.get(0).getName()); assertEquals("B", res.get(1).getName()); } @Test public void typesHavingAMethodNamedBarWhichTakesZeroParams() throws ParseException { InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java"); CompilationUnit cu = JavaParser.parse(is); Function1<MethodDeclaration, Boolean> hasZeroParam = m -> m.getParameters().size() == 0; Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar"); List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(m -> hasZeroParam.apply(m) && isNamedBar.apply(m))); assertEquals(1, res.length()); assertEquals("A", res.get(0).getName()); } @Test public void typesHavingAMethodNamedBarWhichTakesOneParam() throws ParseException { InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java"); CompilationUnit cu = JavaParser.parse(is); Function1<MethodDeclaration, Boolean> hasOneParam = m -> m.getParameters().size() == 1; Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar"); List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(m -> hasOneParam.apply(m) && isNamedBar.apply(m))); assertEquals(1, res.length()); assertEquals("B", res.get(0).getName()); } }
The source file we used in this tests is this one:
class A { void foo() { } void bar() { } } class B { void bar(int x) { } void baz() { } }
This is of course a very, very, very limited introduction to the potentialities of Javaslang. What I thinki is important to get for someone new to functional programming is the tendence to write very small functions which can be composed and manipulates to obtain very flexible and powerful code. Functional programming can seem obscure when we start using it but if you look at the tests we wrote I think they are rather clear and descriptive.
Functional Programming: is all the hype justified?
I think there is a lot of interest in functional programming but if that becomes hype it could lead to poor design decisiong. Think about the time when OOP was the new rising star: the Java designers went all the way down forcing programmers to put every piece of code in a class and now we have utility classes with a bunch of static methods. In other words we took functions and asked them to pretend to be a class to gain our OOP medal. Does it make sense? I do not think so. Perhaps it helped to be a bit extremist to strongly encourage people to learn OOP principles. That is why if you want to learn functional programming you may want to use functional-only languages like Haskell: because they really, really, really push you into functional programming. So that you can learn the principles and use them when it does make sense to do so.
Conclusions
I think functional programming is a powerful tool and it can lead to very expressive code. It is not the right tool for every kind of problem, of course. It is unfortunate that Java 8 comes without proper support for functional programming patterns in the standard library. However some of the enabling features have been introduced in the language and Javaslang is making possible to write great functional code right now. I think more libraries will come later, and perhaps they will help keeping Java alive and healthy for a little longer.
Reference: | Functional programming for Java: getting started with Javaslang from our JCG partner Federico Tomassetti at the Federico Tomassetti blog. |