Bifunctor

This article will look at bifunctors, and how they can be used in the real world. This article assumes some knowlege of functors. A simple way to think about bifunctor is as a functor of two arguments.

A bifunctor is a type class of types which give rise to two independent, covariant functors. Some common uses of bifunctor include mapping over Either and Tuple2 types. While functor provides a map operation that takes a function A = >B and applies that function over a context F[A] => F[B], a bifunctor offers us the bimap operation which takes two functions (A = >B, C => D) and applies one or both of these functions over a context.

In Haskell, we can define a bifunctor as:

class Bifunctor (f:: * -> * -> *) where
  bimap  :: (a -> b) -> (c -> d) -> f a c -> f b d
  first  :: (a -> b) -> f a c -> f b c
  second :: (b -> c) -> f a b -> f a c
  {-# MINIMAL bimap | first, second #-}

A Scala implementation of bimap for both Tuple2 and Either:

trait Functor[F[+_]] {
  def map[A,B](a: F[A])(f: A => B): F[B]
}

trait Bifunctor[F[+_, +_]] {
  def bimap[A, B, C, D](fa: F[A, B], f: A => C, g: B => D): F[C, D]
}

object Bifunctor {
  implicit def Tuple2Bifunctor = new Bifunctor[Tuple2] {
    def bimap[A, B, C, D](fa: (A, B), f: A => C, g: B => D) =
      (f(fa._1), g(fa._2))
  }

  implicit def EitherBifunctor = new Bifunctor[Either] {
    def bimap[A, B, C, D](fa: Either[A, B], f: A => C, g: B => D) =
      fa match {
        case Left(a) => Left(f(a))
        case Right(b) => Right(g(b))
      }
  }
}