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:

scala> Option(1).map(_ + 1)
res0: Option[Int] = Some(2)

scala> List(1,2,3).map(_ + 1)
res1: List[Int] = List(2, 3, 4)

scala> Vector(1,2,3).map(_.toString)
res2: scala.collection.immutable.Vector[String] = Vector(1, 2, 3)

Creating Functor instances

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

scala> import cats._
import cats._

scala> implicit val optionFunctor: Functor[Option] = new Functor[Option] {
     |   def map[A,B](fa: Option[A])(f: A => B) = fa map f
     | }
optionFunctor: cats.Functor[Option] = $anon$1@57248dcd

scala> implicit val listFunctor: Functor[List] = new Functor[List] {
     |   def map[A,B](fa: List[A])(f: A => B) = fa map f
     | }
listFunctor: cats.Functor[List] = $anon$1@2c56ab57

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:

scala> 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
     |   }
function1Functor: [In]=> cats.Functor[[β]In => β]

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:

scala> val len: String => Int = _.length
len: String => Int = <function1>

scala> Functor[List].map(List("qwer", "adsfg"))(len)
res3: List[Int] = List(4, 5)

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

scala> // Some(x) case: function is applied to x; result is wrapped in Some
     | Functor[Option].map(Some("adsf"))(len)
res5: Option[Int] = Some(4)

scala> // None case: simply returns None (function is not applied)
     | Functor[Option].map(None)(len)
res7: Option[Int] = None

Derived methods

lift

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

scala> val lenOption: Option[String] => Option[Int] = Functor[Option].lift(len)
lenOption: Option[String] => Option[Int] = <function1>

scala> lenOption(Some("abcd"))
res8: Option[Int] = Some(4)

fproduct

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

scala> val source = List("a", "aa", "b", "ccccc")
source: List[String] = List(a, aa, b, ccccc)

scala> Functor[List].fproduct(source)(len).toMap
res9: scala.collection.immutable.Map[String,Int] = Map(a -> 1, aa -> 2, b -> 1, ccccc -> 5)

compose

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

scala> val listOpt = Functor[List] compose Functor[Option]
listOpt: cats.Functor[[X]List[Option[X]]] = cats.Functor$$anon$1@48716959

scala> listOpt.map(List(Some(1), None, Some(3)))(_ + 1)
res10: List[Option[Int]] = List(Some(2), None, Some(4))

scala> val optList = Functor[Option] compose Functor[List]
optList: cats.Functor[[X]Option[List[X]]] = cats.Functor$$anon$1@6ba3b150

scala> optList.map(Some(List(1, 2, 3)))(_ + 1)
res11: Option[List[Int]] = Some(List(2, 3, 4))

scala> val listOptList = listOpt compose Functor[List]
listOptList: cats.Functor[[X]List[Option[List[X]]]] = cats.Functor$$anon$1@2d6f6e73

scala> listOptList.map(List(Some(List(1,2)), None, Some(List(3,4))))(_ + 1)
res12: List[Option[List[Int]]] = List(Some(List(2, 3)), None, Some(List(4, 5)))
useful links