El método principal proporcionado por la sintaxis de Functor es map. Es difícil demostrar esto con Opciones y Listas, ya que tienen sus propios métodos de mapa integrados y el compilador de Scala siempre preferirá un método integrado a un método de extensión. Resolveremos esto con dos ejemplos. Primero veamos el map sobre funciones. El tipo Function1 de Scala no tiene un método de map (se llama andThen en su lugar), por lo que no hay conflictos de nombres:
import cats.instances.function._ // for Functor
import cats.syntax.functor._ // for map
val func1 = (a: Int) => a + 1
val func2 = (a: Int) => a * 2
val func3 = (a: Int) => s"${a}!"
val func4 = func1.map(func2).map(func3)
func4(123)
// res3: String = "248!"
Veamos otro ejemplo. Esta vez vamos a abstraernos de los funtores para no trabajar con ningún tipo concreto en particular. Podemos escribir un método que aplique una ecuación a un número sin importar en qué contexto del funtor se encuentre:
def doMath[F[_]](start: F[Int])(implicit functor: Functor[F]): F[Int] = start.map(n => n + 1 * 2)
import cats.instances.option._ // for Functor
import cats.instances.list._ // for Functor
doMath(Option(20))
// res4: Option[Int] = Some(22)
doMath(List(1, 2, 3))
// res5: List[Int] = List(3, 4, 5)
Para ilustrar cómo funciona esto, echemos un vistazo a la definición del método map en cats.syntax.functor. Aquí hay una versión simplificada del código:
implicit class FunctorOps[F[_], A](src: F[A]) {
def map[B](func: A => B)(implicit functor: Functor[F]): F[B] = functor.map(src)(func)
}
El compilador puede usar este método de extensión para insertar un método map donde no haya un map integrado disponible:
foo.map(value => value + 1)
Suponiendo que foo no tiene un método de map incorporado, el compilador detecta el error potencial y envuelve la expresión en un FunctorOps para corregir el código:
new FunctorOps(foo).map(value => value + 1)
El método map de FunctorOps requiere un Functor implícito como parámetro. Esto significa que este código solo se compilará si tenemos un Funtor para F en el alcance. Si no lo hacemos, obtenemos un error del compilador:
final case class Box[A](value: A)
val box = Box[Int](123)
box.map(value => value + 1)
// error: value map is not a member of repl.Session.App0.Box[Int]
// box.map(value => value + 1)
// ^^^^^^^
El método as también está disponible como sintaxis.
List(1, 2, 3).as("As")
// res7: List[String] = List("As", "As", "As")