I stumbled upon this problem when trying to implement a Bifunctior type class for maps (Bifunctor[Map[_, _]]
).
Bifunctor is defined like this in cats:
/**
* The quintessential method of the Bifunctor trait, it applies a
* function to each "side" of the bifunctor.
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val x: (List[String], Int) = (List("foo", "bar"), 3)
* scala> x.bimap(_.headOption, _.toLong + 1)
* res0: (Option[String], Long) = (Some(foo),4)
* }}}
*/
def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]
As the comment states, this function can be called using two functions (in one parameter group) as its input like this: x.bimap(_.headOption, _.toLong + 1)
. This tells me that this is clearly not the bimap
function being called since this one has two parameter groups ((fab: F[A, B])(f: A => C, g: B => D)
). I have been wondering if there is some kind of implicit type conversion that I am not aware of happening here. How does it work? What do I need to implement to get a Bifunctor type class for maps?
An instance of type class Bifunctor
for Map
can be defined as follows
implicit val mapBifunctor: Bifunctor[Map] = new Bifunctor[Map] {
override def bimap[A, B, C, D](fab: Map[A, B])(f: A => C, g: B => D): Map[C, D] =
fab.map { case (k, v) => f(k) -> g(v) }
}
In x.bimap(_.headOption, _.toLong + 1)
implicits are resolved twice:
firstly, instance Bifunctor[Tuple2]
is found (import cats.instances.tuple._
or import cats.instances.all._
or import cats.implicits._
),
secondly, extension method is resolved (import cats.syntax.bifunctor._
or import cats.syntax.all._
or import cats.implicits._
)
Thus x.bimap(_.headOption, _.toLong + 1)
is transformed to
implicitly[Bifunctor[Tuple2]].bimap(x)(_.headOption, _.toLong + 1)
or
Bifunctor[Tuple2].bimap(x)(_.headOption, _.toLong + 1)
or
toBifunctorOps(x)(catsStdBitraverseForTuple2).bimap(_.headOption, _.toLong + 1)