What is a Functor?
Chances are you've already used a Functor. You probably use it everyday irrespective of the language you use.
Paraphrasing Typeclassopedia: "A Functor represent a container of some sort with the ability to apply a function uniformly to every element of that container".
Say we had a List of words and we wanted to find out the lengths of each of those words. We would use a List[String], find the length of each String and get a List[Int] in return.
In scala we could do something like:
We applied the length function to each element of the List "container". What has also happened is that a List[String] has been converted to a List[Int]. We started with a List of words and we end up with a List of word-lengths.
Functors operate on type constructors - which are types that need additional types parameters to be constructed. List[T], Map[K, V], Option[T] and Either[L,R] etc are all type constructors as they need one or more types to be constructed.
A functor can be defined as:
Assuming the F type constructor was List, the above trait could be implemented as:
All Functor implementations traverse over the type supplied and apply the function f, to each element within that type. In the case of List, f is applied to each element of the List.
We could use the ListFunctor as:
This gives us the same results as before, but we've abstracted over the List type constructor and we can covert from List[A] -> List[B] where A and B are any types.
Import points to note are:
1. The container remains the same (F or in the above case List)
2. The supplied function f, works on the value contained within the container.
As per Typeclassopedia: "fmap applies a function to each element of the container without altering the structure of the container"
Let's create our own type constructor to hold a single value. Let's call it Holder:
Now let's define a Functor for Holder:
Here's how we use it:
We converted a Holder[Int] -> Holder[String] by mapping across the value in the Holder.
There are 2 Functor laws:
1. mapping with identity over every item in a container has no effect
2. mapping a composition of two functions over any item in a container is the same as mapping the first function and then mapping the second.
Let's see if HolderFunctor obeys these 2 laws:
Looks like it does obey both laws. :)
Why use Functors?
So here's the real question: Why use Functors? By defining Functors for each container you are interested in, you could define a single function that fmaps across any container containing any type:
Let's try and call it with Holder:
Let's create an implicit Functor[Holder]:
Let's try and use it with Functor[List]:
Let's create an implicit Functor[List]:
What if we want to use it with Option? We simply create an implicit Functor[Option]:
We can now call fmap with Option:
Verifying the laws for Functor[Option]:
We could further simplify fmap as:
Functor has allowed us to define a single fmap function to map across any container for any value type! :)
Here's a listing of the snippets: