Monoids, Functors, and Monads Oh My! – Part III – Functors

If you are coming from an object-oriented programming background, and/or you didn’t like math in school, then the term functors is likely to sound very scary to you.

Functors need not be scary at all, in fact we’ll see in this post that they are a pretty simple concept.  Like monoids, all we have to do is view them from the perspective that they must contain or offer certain functions, and they must obey some laws just like monoids have to obey associative and identity laws as we discussed in parts one and two.

Let’s go back to our proverbial nutshell.  In said nutshell, a functor is nothing more than some class that contains a map function and obeys what are called functor laws.

What is a map function?  Here is the simplest way I can explain it using as little jargon as possible:

In functional programming, a map function is a function which takes a function and some collection or container-like object and goes through that collection and maps, or translates each element to a different element using the given function.

*Remember that in functional programming, functions are first-class entities, meaning they can be passed around and used as parameters or returned as results.

So the scala representation would look like this:

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

This will become a bit clearer when we look over a concrete example, but let’s just focus for now on the relevant parts to our discussion.  To summarize what map is doing here, take a look at what it takes and what it returns.  It takes some container type,

(fa: F[A])

and a function which ‘maps or translates‘ a type A to a type B:

(f: A => B)

and returns some container type F[B].

Let’s look at a more concrete example:

val listFunctor = new Functor[List] {
  def map[A,B](as: List[A])(f: A => B): List[B] = as map f
}

You can see here our container type as referenced above is List.  In the above definition then, since the map function is parametric on types A and B, then this implementation will basically take a List[A] and return to you back a List[B].

The implementation of the function (f: A => B) is completely irrelevant for the purposes of this map implementation.  All it needs to know is that it’s getting a function that takes an A and returns a B, and that function will be applied to every element in that container type, in this case a List.

It’s important to realize that the implementation of map in functors is structurally sound, that is, the container type that is returned is completely identical to the original container type that is mapped over in every way except for the type.  It does not add or remove elements or reorder them.  It’s also worth special mention that A and B can be the same type, i.e. Int => Int or String => String.

Identity

I mentioned earlier that functors must obey some laws, in addition to defining map.  One of these is the identity law:

map(a)(b => b) == a

All this says is that mapping over a structure a, should just return a when the function given is the identity function; identity function being obviously a function that always returns the same value as its argument.  This law is necessary to prove what we mentioned above about preserving structure but we won’t go into details why.

So there you have a straightforward explanation of functors, or as straightforward as I can deliver it.

The concept of functors is important for our next step which is to tackle monads.  This is because all monads are functors.  That’s right, monads are just a subclass of functors in general as we’ll see.

Until next time brothers and sisters, thanks for reading \,,/


Also published on Medium.

Leave a Reply

Your email address will not be published. Required fields are marked *