Bifunctor

intermediate

Bifunctor is a lot like Functor. It offers a nice solution for those times when you don’t want to ignore the leftmost type argument of a binary type constructor, such as Either or Tuple2.

Its core operation, bimap, closely resembles map, except it lifts two functions into the new context, allowing you to apply one or both.

fun Kind2<F, A, B>.bimap(fl: (A) -> C, fr: (B) -> D): Kind2<F, C, D>

bimap takes two unary functions and a binary type constructor as a receiver such as Tuple2(1, 3) or Left(5) and applies whichever function it can – both if it can!

import arrow.*
import arrow.core.*
import arrow.typeclasses.*

fun <F> greet(BF: Bifunctor<F>, p: Kind2<F, String, String>): Kind2<F, String, String> =
    BF.run { p.bimap({ "Hello $it" }, { "General $it" }) }

greet(Either.bifunctor(), Left("there")) // Left("Hello there")    
// Left(a=Hello there)
greet(Either.bifunctor(), Right("Kenobi")) // Right("General Kenobi")
// Right(b=General Kenobi)
greet(Tuple2.bifunctor(), Tuple2("there", "Kenobi")) // Tuple2("Hello there", "General Kenobi")
// Tuple2(a=Hello there, b=General Kenobi)

So, bimap is map but for binary type constructors where you want the ability to lift two functions at once.

Main Combinators

Kind2<F, A, B>#bimap

Transforms the inner contents of a binary type constructor

fun Kind2<F, A, B>.bimap(fl: (A) -> C, fr: (B) -> D): Kind2<F, C, D>

val tuple2Bifunctor = Tuple2.bifunctor()
tuple2Bifunctor.run { Tuple2(4, 4).bimap({ it + 1 }, { it - 1 }) }
// Tuple2(a=5, b=3)

Other combinators

For a full list of other useful combinators available in Bifunctor see the Source

Laws

Arrow provides BifunctorLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own Bifunctor instances.

Data Types

The following datatypes in Arrow provide instances that adhere to the Bifunctor typeclass.