1

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.

1

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I still don't get the part when you said F to be dynamic. As an example you said about turning F[G[A]] into G[F[A]]. When do we need to do that?
For instance when you have List[A] which you can map into List[Future[B]] to run things in parallel, and then to get back to Future[List[B]] - which would be better than parallel collection because: 1. they are buggy 2. by doing things yourself you have much better control over which threads are used, is there any retry, etc. And this is just one example where typeclasses are useful - see fs2.io for example where F can be defined at callsite and yet whole library works not knowing what F is.
2

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 flatMappable things you need Monad etc.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.