Bifunctor

Формальное определение

Bifunctor - тип, порождающий два несвязанных функтора.

Определение в виде кода на Scala

trait Bifunctor[F[_, _]]:
  /** `map` over both type parameters. */
  def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]

  /** Extract the Functor on the first param. */
  def leftFunctor[R]: Functor[[X] =>> F[X, R]] =
    new Functor[[X] =>> F[X, R]]:
      extension [A](fax: F[A, R]) override def map[B](f: A => B): F[B, R] = leftMap(fax)(f)

  def leftMap[A, B, C](fab: F[A, B])(f: A => C): F[C, B] =
    bimap(fab)(f, identity)

  /** Extract the Functor on the second param. */
  def rightFunctor[L]: Functor[[X] =>> F[L, X]] =
    new Functor[[X] =>> F[L, X]]:
      extension [A](fax: F[L, A]) override def map[B](f: A => B): F[L, B] = rightMap(fax)(f)

  def rightMap[A, B, D](fab: F[A, B])(g: B => D): F[A, D] =
    bimap(fab)(identity, g)

  /** Unify the functor over both params. */
  def uFunctor: Functor[[X] =>> F[X, X]] =
    new Functor[[X] =>> F[X, X]]:
      extension [A](fax: F[A, A]) override def map[B](f: A => B): F[B, B] = umap(fax)(f)

  def umap[A, B](faa: F[A, A])(f: A => B): F[B, B] =
    bimap(faa)(f, f)

Примеры

Either

given Bifunctor[Either] with
  override def bimap[A, B, C, D](fab: Either[A, B])(f: A => C, g: B => D): Either[C, D] =
    fab match
      case Right(value) => Right(g(value))
      case Left(value)  => Left(f(value))

Writer - функциональный журнал

case class Writer[W, A](run: () => (W, A))

given Bifunctor[Writer] with
  override def bimap[A, B, C, D](fab: Writer[A, B])(f: A => C, g: B => D): Writer[C, D] =
    Writer { () =>
      val (a, b) = fab.run()
      (f(a), g(b))
    }

Реализация

Реализация в ScalaZ

import scalaz.*
import Scalaz.*

Bifunctor[Tuple2].bimap(("asdf", 1))(_.toUpperCase, _ + 1)                               
// ("ASDF",2)
("asdf", 1).bimap(_.length, _ + 1)                                                       
// (4,2)

// Для суммированных типов, какая функция применяется, зависит от того, какое значение присутствует:
Bifunctor[Either].bimap(Left("asdf") : Either[String,Int])(_.toUpperCase, _ + 1)         
// Left("ASDF")
Bifunctor[Either].bimap(Right(1): Either[String,Int])(_.toUpperCase, _ + 1)              
// Right(2)

//
// leftMap / rightMap
//

// Существуют функции для отображения только «правого» или «левого» значения:
Bifunctor[Tuple2].leftMap(("asdf", 1))(_.substring(1))                                   
// ("sdf" -> 1)
Bifunctor[Tuple2].rightMap(("asdf", 1))(_ + 3)                                           
// ("asdf" -> 4)

// Они идут с таким синтаксисом
1.success[String].rightMap(_ + 10)                                                       
// Success(11)
("a", 1).rightMap(_ + 10)                                                                
// ("a" -> 11)

// и еще более причудливым
1.success[String] :-> (_ + 1)                                                            
// Success(2)

// С левой стороны вывод типа может быть плохим, так что мы вынуждены явно указывать типы в функции, которую мы оставилиMap.
val strlen: String => Int = _.length
(strlen <-: ("asdf", 1))                                                                 
// (4 -> 1)
(((_:String).length) <-: ("asdf", 1))                                                    
// (4 -> 1)

strlen <-: ("asdf", 1) :-> (_ + 1)                                                       
// (4 -> 2)

//
// Functor extraction
//

// Мы можем получить либо левый, либо правый базовые функторы.
val leftF = Bifunctor[\/].leftFunctor[String]
leftF.map("asdf".right[Int])(_ + 1)                                                      
// "asdf".right[Int]
leftF.map(1.left)(_ + 1)                                                                 
// 2.left[String]

val rightF = Bifunctor[\/].rightFunctor[String]
rightF.map("asdf".left[Int])(_ + 1)                                                      
// "asdf".left[Int]
rightF.map(1.right)(_ + 1)                                                               
// 2.right[String]

//
// Ufunctor
//

// Если у нас есть F[A,A] (вместо F[A,B] с разными A и B) мы можем извлечь "унифицированный функтор", который является функтором,
Bifunctor[Tuple2].uFunctor.map((2,3))(_ * 3)                                             
// (6 -> 9)

// или пропустить шаг извлечения единого функтора методом umap.
Bifunctor[Tuple2].umap((2,3))(_ * 3)                                                     
// (6 -> 9)

Ссылки: