Functors in Java
TweetJava is not a functional language. Java does not support functions as values which can be passed around as parameters to other functions. Although, a similar sort of functionality can be implemented using Function Objects. Many people confuse function objects and Functors which are actually quite different things. In this article we focus on Functors but to be able to demonstrate we need to first be able to define function objects which can be passed around as values.
Java 8 adds support for lambda expressions which are a nothing but some sugar for Anonymous classes from earlier versions Java and functions returned from lambda expression are objects which implement a usual interface with only one method. Here we will be using these lambda expressions but similar functionality can be obtained by using Anonymous class syntax by providing inline implementation of these interfaces. Java 8 also introduce default methods for interface which again we will be using in this article, but similar functionality can be obtained by using Abstract class instead, in earlier versions of Java.
As a first step lets define some simple interfaces for function objects, we will be adding more features to these interfaces as we go along.
Function1 and Function2 are interfaces for functions with 1 and 2 input parameters respectively. Similarly, We could also define a Function0,Function3, Function4 etc. interfaces for functions which do not take any input parameter and take 3 and 4 or more parameters. Now lets create some functions and invoke them.
We defined some simple functions which take Integers as input and return Integer as output. Since, in our interfaces we define separate type parameters for each parameter and return type we can function which take and return different types. Let’s write a function which takes Age as Integer in years and Height as Double in cm, and returns String with Age and Height separated by “-“
Many functional languages also provide ability to apply a function partially, that is, provide only first few parameters instead of all parameters. When this is done function returns another function which takes remaining parameters, this is called currying and is a very useful feature. Again, it is simple to add this currying to our Function Objects. Lets add this capability to our Function interfaces.
Lets see currying in action
Functor
Now lets say we have a List of Integers and we want to calculate square of all the Integers in the List. One obvious way is to iterate over each element of the list and pass the value of each item to our square function. And lets say we also want to have all the resulting integers also as a List, so we need to create a new List of Integers and resulting integers to this new list. This is a very common pattern that appears again and again in functional programming. That is, we have a function from input type A to return type B, and a List of type A and we want List of type B created by application of function on each element. This is applicable to all container types like Sets, Lists, Tree,Vector etc, in fact, it applies to types other than container types, only requirement is that Type in question must have at least one type variable, we will see example of a type other than container type later in this blog. For example, List
Here we first calculated square of all numbers in the list of integers. We also chained fmap to first multiply all numbers by square and the fmap resulting list with partially applied mult function with only first parameter specified as 4, this further multiplies numbers in list by 4. This chaining of functions, that is, of applying one function after another is common. And there exist another more elegant way of doing this, which is called function composition. In function composition instead of applying one function first and then applying second function on the result, we create a new function by composing these two functions. This new function when applied to list has the same effect. We can achieve function composition if we also make our Function Object Functors too, that is, have Function Object also implement fmap method. This is an example where our type is not a container type but still can be Functor. As explained previously that only requirement of a potential Functor type is that it must have at least one type parameter.
This makes our Function Objects Functors too. fmap on Function Object is same as function composition and using that we created a new function square_and_mult4, this function when mapped of elements of list produces same result as applying square and mult individually. Although, we could implement such a functionality as available in more functional languages such as Haskell but there are some weaknesses in type system of java. For example, when implementing Functor interface we had to again specify implementing type (FList), also there is no way to enforce constraint in the interface that only types which have at least one type parameter should be able to implement this interface. Also, we could declare functions using lambda system but we still had to provide all the types in the LHS, whereas, languages like Haskell can auto deduce this type information. These are some areas where languages like Haskell, scala have real advantage over java.