Functor

A Functor is a ubiquitous type class involving types that have one "hole", i.e. types which have the shape F[?], such as Option, List and Future. (This is in contrast to a type like Int which has no hole, or Tuple2 which has two holes (Tuple2[?,?])).

The Functor category involves a single operation, named map:

def map[A, B](fa: F[A])(f: A => B): F[B]

This method takes a function A => B and turns an F[A] into an F[B]. The name of the method map should remind you of the map method that exists on many classes in the Scala standard library, for example:

Option(1).map(_ + 1)
List(1, 2, 3).map(_ + 1)
Vector(1, 2, 3).map(_.toString)

Creating Functor instances

We can trivially create a Functor instance for a type which has a well behaved map method:

import cats._

implicit val optionFunctor: Functor[Option] = new Functor[Option] {
  def map[A, B](fa: Option[A])(f: A => B) = fa map f
}

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

However, functors can also be created for types which don't have a map method. For example, if we create a Functor for Function1[In, ?] we can use andThen to implement map:

implicit def function1Functor[In]: Functor[Function1[In, ?]] =
  new Functor[Function1[In, ?]] {
    def map[A, B](fa: In => A)(f: A => B): Function1[In, B] = fa andThen f
  }

This example demonstrates the use of the kind-projector compiler plugin This compiler plugin can help us when we need to change the number of type holes. In the example above, we took a type which normally has two type holes, Function1[?,?] and constrained one of the holes to be the In type, leaving just one hole for the return type, resulting in Function1[In,?]. Without kind-projector, we'd have to write this as something like ({type F[A] = Function1[In,A]})#F, which is much harder to read and understand.

Using Functor

map

List is a functor which applies the function to each element of the list:

Functor[List].map(List("qwer", "adsfg"))(_.length)

Option is a functor which only applies the function when the Option value is a Some:

Functor[Option].map(Option("Hello"))(_.length) should be(res0)
Functor[Option].map(None: Option[String])(_.length) should be(res1)

Derived methods

lift

We can use Functor to "lift" a function from A => B to F[A] => F[B]:

val lenOption: Option[String] => Option[Int] = Functor[Option].lift(_.length)
lenOption(Some("abcd"))

We can now apply the lenOption function to Option instances.

val lenOption: Option[String] ⇒ Option[Int] = Functor[Option].lift(_.length)
lenOption(Some("Hello")) should be(res0)

fproduct

Functor provides an fproduct function which pairs a value with the result of applying a function to that value.

val source = List("Cats", "is", "awesome")
val product = Functor[List].fproduct(source)(_.length).toMap

product.get("Cats").getOrElse(0) should be(res0)
product.get("is").getOrElse(0) should be(res1)
product.get("awesome").getOrElse(0) should be(res2)

compose

Functors compose! Given any functor F[_] and any functor G[_] we can create a new functor F[G[_]] by composing them:

val listOpt = Functor[List] compose Functor[Option]

In the previous example the resulting functor will apply the map operation through the two type constructors: List and Option.

val listOpt = Functor[List] compose Functor[Option]
listOpt.map(List(Some(1), None, Some(3)))(_ + 1) should be(res0)