You can call .map on object, when you know that this object has this method, and that this method is called this way. If you know exact type of the object, then compiler can check that, indeed, this is the case. But what if you don't know the type of the object? And what if you don't want to use runtime reflection?
Imagine situation like this:
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] = fInt.map(_ * 2)
Here, we don't know the type of F - it might be List, Option, Future, IO, Either[String, *]. And yet we are able to .map over it without using reflection - we use Functor[F] to power extension methods. We could do it also without extension method like this:
def doubleIntInF[F[_]: Functor](fInt: F[Int]): F[Int] = Functor[F].map(fInt)(_ * 2)
and it would work (as long as we have the right implicits in scope):
doubleIntInF(List(1,2,3,4,5,6)) // List(2, 4, 6, 8, 10, 12 doubleIntInF(Option(4)) // Some(2)
In cases, where we know, that F=List, Option, etc - we have no reason to use it. But we have all the reasons to use if this F is dynamic.
And why would we like to make this F dynamic? To use it in libraries which e.g. could combine several functionalities provided through type classes together.
E.g. if you have F[_] and G[_] and you have Traverse[F] and Applicative[G] (more powerful Functor) you are able to turn F[G[A]] into G[F[A]]:
val listOption: List[Option] = List(Some(1), Some(2)) listOption.sequence // Some(List(1, 2)) val listFuture: List[Option] = List(Future(1), Future(2)) listFuture.sequence // Future(List(1, 2))
Virtually all libraries in Cats ecosystem use this concept (called typeclasses) to implement functionality without assuming that you pick the same data structures and IO components as they would. As long as you can provide typeclass instances that proves that they can safely use some methods on your type, they can implement the functionality (e.g. Cats Effect are extending Cats with some typeclasses and Doobie, FS2, Http4s, and so on build on top of these without making assumption about what you use to run your computations).
So long story short - in cases like your it doesn't make sense to use Functor, but in general they enable you to still use .map in situations which are NOT as simple and you don't have a hardcoded type.