Apply extends the Functor type class (which features the familiar map
function) with a new function ap. The ap function is similar to map
in that we are transforming a value in a context (a context being the F in F[A];
a context can be Option, List or Future for example).
However, the difference between ap and map is that for ap the function that
takes care of the transformation is of type F[A => B], whereas for map it is A => B:
Here are the implementations of Apply for the Option and List types:
import cats._
implicit val optionApply: Apply[Option] = new Apply[Option] {
  def ap[A, B](f: Option[A => B])(fa: Option[A]): Option[B] =
    fa.flatMap(a => f.map(ff => ff(a)))
  def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa map f
  def product[A, B](fa: Option[A], fb: Option[B]): Option[(A, B)] =
    fa.flatMap(a => fb.map(b => (a, b)))
}
implicit val listApply: Apply[List] = new Apply[List] {
  def ap[A, B](f: List[A => B])(fa: List[A]): List[B] =
    fa.flatMap(a => f.map(ff => ff(a)))
  def map[A, B](fa: List[A])(f: A => B): List[B] = fa map f
  def product[A, B](fa: List[A], fb: List[B]): List[(A, B)] =
    fa.zip(fb)
}Since Apply extends Functor, we can use the map method from Functor:
import cats.implicits._
val intToString: Int => String = _.toString
val double: Int => Int = _ * 2
val addTwo: Int => Int = _ + 2
Apply[Option].map(Some(1))(intToString) should be(res0)
Apply[Option].map(Some(1))(double) should be(res1)
Apply[Option].map(None)(addTwo) should be(res2)And like functors, Apply instances also compose:
val listOpt = Apply[List] compose Apply[Option]
val plusOne = (x: Int) => x + 1
listOpt.ap(List(Some(plusOne)))(List(Some(1), None, Some(3))) should be(res0)The ap method is a method that Functor does not have:
Apply[Option].ap(Some(intToString))(Some(1)) should be(res0)
Apply[Option].ap(Some(double))(Some(1)) should be(res1)
Apply[Option].ap(Some(double))(None) should be(res2)
Apply[Option].ap(None)(Some(1)) should be(res3)
Apply[Option].ap(None)(None) should be(res4)Apply also offers variants of ap. The functions apN (for N between 2 and 22)
accept N arguments where ap accepts 1.
Note that if any of the arguments of this example is None, the
final result is None as well.  The effects of the context we are operating on
are carried through the entire computation:
val addArity2 = (a: Int, b: Int) => a + b
Apply[Option].ap2(Some(addArity2))(Some(1), Some(2)) should be(res0)
Apply[Option].ap2(Some(addArity2))(Some(1), None) should be(res1)
val addArity3 = (a: Int, b: Int, c: Int) => a + b + c
Apply[Option].ap3(Some(addArity3))(Some(1), Some(2), Some(3)) should be(res2)Similarly, mapN functions are available:
Apply[Option].map2(Some(1), Some(2))(addArity2) should be(res0)
Apply[Option].map3(Some(1), Some(2), Some(3))(addArity3) should be(res1)Similarly, tupleN functions are available:
Apply[Option].tuple2(Some(1), Some(2)) should be(res0)
Apply[Option].tuple3(Some(1), Some(2), Some(3)) should be(res1)In order to use functions apN, mapN and tupleN *infix*,
import cats.implicits._.
import cats.implicits._
val option2 = (Option(1), Option(2))
val option3 = (option2._1, option2._2, Option.empty[Int])
option2 mapN addArity2 should be(res0)
option3 mapN addArity3 should be(res1)
option2 apWith Some(addArity2) should be(res2)
option3 apWith Some(addArity3) should be(res3)
option2.tupled should be(res4)
option3.tupled should be(res5)