Wednesday, February 16, 2011

Functor

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.

For example:



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"

Some Examples



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.

Functor Laws


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:

No comments: