Podemos definir un funtor simplemente definiendo su método map. Aquí hay un ejemplo de un Functor para Option, aunque tal cosa ya existe en cats.instances. La implementación es trivial: simplemente llamamos al método de mapa de Option:
implicit val optionFunctor: Functor[Option] = new Functor[Option] {
def map[A, B](value: Option[A])(func: A => B): Option[B] = value.map(func)
}
A veces necesitamos inyectar dependencias en nuestras instancias. Por ejemplo, si tuviéramos que definir un Functor personalizado para Future (otro ejemplo hipotético: Cats proporciona uno en cats.instances.future), tendríamos que tener en cuenta el parámetro ExecutionContext implícito en future.map. No podemos agregar parámetros adicionales a functor.map, por lo que debemos tener en cuenta la dependencia cuando creamos la instancia:
import scala.concurrent.{Future, ExecutionContext}
implicit def futureFunctor(implicit ec: ExecutionContext): Functor[Future] = new Functor[Future] {
def map[A, B](value: Future[A])(func: A => B): Future[B] = value.map(func)
}
Cada vez que llamamos a un Functor for Future, ya sea directamente usando Functor.apply o indirectamente a través del método de extensión map, el compilador ubicará futureFunctor por resolución implícita y buscará recursivamente un ExecutionContext en el sitio de la llamada. Así es como se vería la expansión:
// We write this:
Functor[Future]
// The compiler expands to this first:
Functor[Future](futureFunctor)
// And then to this:
Functor[Future](futureFunctor(executionContext))