简体   繁体   中英

Why is Functor in scala cats required

I have just started to learn Scala cats framework. I am reading Functor . I understood its features but I don't understand it's usage. Why should I use it if there is a map method already available in Functors like List , Option etc.

As an example,

val list = List(1, 2, 3)
Functor[List].map(list)(x => x * 2)

But the same can be achieved with

list.map(x => x * 2)

What do we get when we abstract the method map inside Functor trait. Can someone please throw some light on it so that I understand its usage.

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 eg could combine several functionalities provided through type classes together.

Eg 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 (eg 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.

The question is similar to why in OOP someone needs an interface (trait) while its implementations have the same methods.

Interface is an abstraction. Abstractions are useful. While programming we prefer to focus on significant things and ignore details that are currently not significant. Also programming with interfaces helps to decouple entities creating better architecture.

Interfaces (like Comparable , Iterable , Serializable etc.) is the way to describe behavior in OOP. Type classes (like Functor , Monad , Show , Read etc.) is the way to describe behavior in FP.

If you just want to map (or flatMap ) over a list (or an Option ) you don't need Functor . If you want to work with all mappable things you need Functor , if you want to work with all flatMap pable things you need Monad etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM