scalabook
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)
Ссылки:
- Category Theory for Programmers - Bartosz Milewski:
- Herding Cats
- Scalaz API
- The Dao of Functional Programming - Bartosz Milewski: