0

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?

Florian Baierl
  • 2,378
  • 3
  • 25
  • 50
  • 4
    Indeed there are implicit conversions happening. For the example from docs we have something like `toBifunctorOps(x)(catsStdBitraverseForTuple2).bimap(_.headOption, _.toLong + 1)` – Mario Galic Sep 26 '19 at 08:04

1 Answers1

5

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)
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66