A Small Functor Example

In the world of functional programming you will quickly come across the concept of a Functor. What I present below is a simple example that might provide some intuition, with references for further reading.

What’s the Problem?

We often want to use a function that takes an instance of A and returns an instance of B in a context where we have an instance of M[A] and need an instance of M[B]. The most common example is the map method on List, Option, etc.

So the problem is:

Given a function f that takes an A and returns a B, how can we construct a new function that takes an M[A] and returns an M[B] for any M?

Functors

A Functor accepts a function, A ⇒ B, and returns a new function M[A] ⇒ M[B], where M is any kind.

So without further ado, here’s the code:

object Functorise extends App {
 
  // This is a Functor
  trait Functor[M[_]] {
 
    /* convert f into a function mapping M[A] to M[B]
     * eg. if M were List, and f was Int ⇒ String
     * fmap would yield List[Int] ⇒ List[String]
     */
    def fmap[A, B](f: A ⇒ B): M[A] ⇒ M[B]
  }
 
  /* Here are a couple of examples for Option and List Functors
   * They are implicit so they can be used below in enrichWithFunctor
   */
 
  implicit object OptionFunctor extends Functor[Option] {
    def fmap[A, B](f: A ⇒ B): Option[A] ⇒ Option[B] 
      = option ⇒ option map f
  }
 
  implicit object ListFunctor extends Functor[List] {
    def fmap[A, B](f: A ⇒ B): List[A] ⇒ List[B] 
      = list ⇒ list map f
  }
 
  /* enrichWithFunctor is an implicit to enrich any kind 
   * with an fmap method.
   * List, Option and any other Foo[X] can be enriched with the
   * new method.
   */
  implicit def enrichWithFunctor[M[_], A](m: M[A]) = new {
 
    /* fmap requires an implicit functor, whose type is M, to which it
     * delegates to do the real work
     */
    def mapWith[B](f: A ⇒ B)(implicit functor: Functor[M]): M[B] 
      = functor.fmap(f)(m)
  }
 
  // some examples
 
  println(List(1, 2) mapWith (_ + 1)) // List(2, 3)
 
  println(some(1) mapWith (_ + 1) mapWith (_ * 3)) // Some(6)
 
  println(none[Int] mapWith (_ + 1)) // None
 
  def some[A](a: A): Option[A] = Some(a)
  def none[A]: Option[A] = None
}

What is Going On?

Lets take the List(1, 2) mapWith (_ + 1) example and go through it.

There is no mapWith method on List, so an implicit method is searched for that can turn the List into something that does have a mapWith method. Thus, enrichWithFunctor is found and called with the List(1, 2) which returns an anonymous class with the mapWith method required.

By the way, to understand more about that M[_], watch Daniel Spiewak’s High Wizardry in the Land of Scala talk.

That anonymous class also has a type parameter, M, which has now been fixed to List, and so mapWith in this class looks like this:

def mapWith[B](f: A ⇒ B)(implicit functor: Functor[List]): List[B] 
  = functor.fmap(f)(m)

The mapWith method requires an implicit Functor[List] to be available, and the ListFunctor object is just that. So when functor.fmap(f) is called, it is the ListFunctor’s fmap method being called.

ListFunctor.fmap(f) takes a function, in our example its (i:Int) ⇒ i + 1, and returns a new function (list:List[Int]) ⇒ list.map(_ + 1).

Finally, m, the List(1, 2) we started with that was enriched with the mapWith method, is applied to the new function which results in a new List.

But Why?

All we’ve done here is create a complicated looking bunch of code that just ends up calling methods on Option and List that already exist, specifically the map method.

Yes, in these examples thats true.

The real idea is that we’ve created a general abstraction for all kinds, including those that don’t have a map method. All a user of this code needs to do is have an implicit Functor in scope for the kind they would like to map over.

Furthermore, this is a building block for some interesting things that come next like Applicative Functors.

Finally, you don’t need to implement this yourself, Scalaz has it already for you to exploit.

Further Reading

Comments are closed.