What Is a Functor? Basic Theory for Java Developers
Are you a Java developer who wants to know the theory behind Functors? Here you will find a step-by-step tutorial that will help you understand them.
Join the DZone community and get the full member experience.
Join For FreeIn general, a Functor is a concept originating from mathematics, to be exact from a part of mathematics called category theory. In my very first article, I described in detail a similar concept known as Monad. Here I want to continue that thread, so I will try to give you some more insight into Functors — what they are, how they work, and what the theory behind them is. I will also implement a simple functor to better understand how they work and why using them may be a clearer solution.
The source code for this article is available in GitHub repository.
Why Even Care About Functors?
Firstly, functors are probably the simplest of commonly known functional containers, and understanding their mechanics can help while working with more complex containers like monads or applicatives.
Secondly, it is quite a handy concept, especially helpful when you need to perform operations over some value inside the container.
Finally, unlike monads, which have quite a few counterparts in Java, functors lack a good built-in equivalent, at least as far as I could check, so it may come in handy someday to know the concept. It can make your life easier.
What Is a Functor?
As I mentioned above, a Functor is a concept from category theory and represents the mapping between two categories. In software, functors can be viewed as a util class that allows us to perform a mapping operation over values wrapped in some context. Despite being quite easy to implement with the use of a simple interface, we must keep in mind that to honestly call our piece of code a functor, we have to fulfill laws applying to functions in category theory - more about this below.
Also, contrary to monads from the first article, there are no good built-in counterparts of functors in Java and other modern-day programming languages. Probably the best counterpart can be found in the Scala library called Cats.
Functor Laws
As most of the concepts from mathematics moved to programming, Functors also have some theoretical background behind them. In the case of functors, a part of this background is their laws, namely Identity and Associativity. Below, I will try to present them in a more illustrative way with the use of a ReferntialFunctor class of my own making.
Assumptions about functions used in the associativity part:
- f is a function mapping from type T to type R
- g is a function mapping from type R to type U
Identity:
Mapping values inside the functor with the identity function should always return an unchanged value.
JavaReferentialFunctor<Integer> identity = new ReferentialFunctor<>(x).map(Function.identity()); identity.valueEquals(x);
Associativity:
This law is the same as the one in the case of monads so it states that in the chain of function applications, it should not matter how functions are nested.
JavaFunction<Integer, String> f = Object::toString; Function<String, Long> g = Long::valueOf; ReferentialFunctor<Long> leftSide = new ReferentialFunctor<>(x).map(f).map(g); ReferentialFunctor<Long> rightSide = new ReferentialFunctor<>(x).map(f.andThen(g)); leftSide.equals(rightSide);
Creating a Functor
Similar to a monad, we will start with the creation of a parameterized type M<T>, a wrapper for the value of type T inside. However, unlike in the case of a monad, we will have to implement only one method:
map responsible for performing operations. Here we pass a function that operates on value in our context and returns it with another type already wrapped in context. This method should have the following signature M<R> (T -> R).
After expelling the mathematical context behind a Functor and what method it required, we can start implementing it.
Implementing a Functor
package org.pasksoftware.functor.example;
import org.pasksoftware.functor.Functor;
import java.util.function.Function;
public class WrapperWithFunctor<A> implements Functor<A> {
private final A value;
public WrapperWithFunctor(A value) {
this.value = value;
}
@Override
public <B> WrapperWithFunctor<B> map(Function<A, B> f) {
return new WrapperWithFunctor<>(f.apply(value));
}
// For sake of asserting in Example
boolean valueEquals(A x) {
return value.equals(x);
}
}
Here we are with ready Functor implementation, time to explain what happened in these 20 lines of code.
What Exactly Happened Above
The base of our implementation is the parameterized class with the immutable field named "value", which is responsible for storing our value. Then, we have a constructor, which actually makes it possible to instantiate our small functor implementation.
Next, we have the meat of this snippet, the map method that will guarantee that we are able to fulfill the required conditions in the form of functor laws.
With all the code described, it is time for an example of functors at work.
package org.pasksoftware.functor.example;
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
int x = 2;
Function<Integer, String> f = Object::toString;
// Task: mapping over the value inside the container object.
// Non-functor
Wrapper<Integer> wrapper = new Wrapper<>(x);
String mappedValue = f.apply(wrapper.value);
// One liner - Wrapper<String> mappedWrapper = new Wrapper<>(f.apply(x));
Wrapper<String> mappedWrapper = new Wrapper<>(mappedValue);
// Functor
WrapperWithFunctor<Integer> wrapperFunctor = new WrapperWithFunctor<>(x);
WrapperWithFunctor<String> mappedWrapperFunctor = wrapperFunctor.map(f);
assert mappedWrapperFunctor.valueEquals(mappedWrapper.value);
System.out.println("Values inside wrappers are equal");
}
}
In the above snippet, you could see why functors can be useful apart from their somewhat simple structure. You can notice that performing the same operations without using functors required some changes in our API design and some additional boilerplate to make it more readable.
Summing up
Functors can be quite useful even besides their simple structure. It is the simplest of commonly known containers from category theory and knowing how it works can help you further in your journey. What is more, a functor-based approach can provide a more descriptive and clearer solution for operating on values inside containers of any type. Thank you for your time.
If you enjoyed reading about Functors, I can recommend you read my first article linked earlier.
Frequently Asked Questions about Functor
What Is Functor?
Functor is a concept from category theory and represents the mapping between two categories.
What Are Functor Laws?
Every Functor implementation has to satisfy two laws: Identity, and Associativity.
What Situation Do I Need to Implement Functor?
To implement Functor, you need a parameterized type M<T> and a method map.
Opinions expressed by DZone contributors are their own.
Comments