classDiagram
Bifoldable~P[+_,+_]~ <|-- Bitraverse~P[+_,+_]~
Bifunctor~P[+_,+_]~ <|-- Bitraverse
Bifunctor <|-- Biapply~P[+_,+_]~
Biapply <|-- Biapplicative~P[+_,+_]~
Functor~F[+_]~ <|-- Bifunctor
Functor <|-- Bifunctor
Functor <|-- Profunctor~P[-_,+_]~
Bifunctor <|-- Zivariant~Z[-_,+_,+_]~
Profunctor <|-- Zivariant
class Functor {
) map(F[A], A => B): F[B]
}
class Profunctor {
) dimap(AA => A, B => BB): P[A,B] => P[AA,BB]
}
class Bifunctor {
) bimap(A => AA, B => BB): P[A,B] => P[AA,BB]
}
class Bifoldable {
) bifoldLeft(F[A,B], C, (C,A) => C, (C,B) => C): C
}
class Bitraverse {
) bitraverse[G: Applicative](F[A,B], A=>G[C], B => G[D]): G[F[C,D]]
}
class Biapply {
) biApply(F[A,B], F[A=>AA,B=>BB]): F[AA,BB]
}
class Biapplicative {
) bipure(a: A, b: B): F[A,B]
}
class Zivariant {
) zimap(AA => A, B => BB, C => CC): P[A,B,C] => P[AA,BB,CC]
}
Abstracts over type constructor with 2 "holes". Represents two independent functors:
trait Bifunctor[F[_, _]] {
def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]
}
- Bifunctor Laws
- identity
xs.bimap(identity, identity) == xs
bimap with two identify function does nothing
flowchart LR
PAB1(("P[A,B]")) --"bimap(id,id)"--> PAB2(("P[A,B]"))
- composition
xs.bimap(f, h).bimap(g,i) == xs.bimap(x => g(f(x), x => h(i(x))
you can bimap using f and h and then bimap using g and i or bimap once using composition
Second law is automatically fulfilled if the first law holds.
flowchart LR
PAB(("P[A,B]")) --"bimap(f1 andThen f2, g1 andTHen g2)"--> PAABB(("P[A2,B2]"))
PAB(("P[A,B]")) --"bimap(f1,g1)"--> PA1B1(("P[A1,B1]"))--"bimap(f2,g2)"--> PAABB(("P[A2,B2]"))
- Alternatively can be specified by providing
def leftMap[A, B, C](fab: F[A, B])(f: A => C): F[C, B]
def rightMap[A, B, D](fab: F[A, B])(g: B => D): F[A, D]
In that case identity law must hold for both functions:
3. identity xs.leftMap(identity) == xs
leftMap with identify function does nothing
flowchart LR
PABL1(("P[A,B]")) --"leftMap(id)"--> PABL2(("P[A,B]"))
- identity
xs.rightMap(identity) == xs
rightMap with identify function does nothing
flowchart LR
PABR1(("P[A,B]")) --"rihtMap(id)"--> PABR2(("P[A,B]"))
If leftMap and rightMap and bimap are specified then additional law must be fullfilled:
5. xs.bimap(f, g) == xs.leftMap(f).rightMap(g)
flowchart LR
PAB(("P[A,B]")) --"bimap(f, g)"--> PAABB(("P[A2,B2]"))
PAB(("P[A,B]")) --"leftMap(f)"--> PA1B1(("P[A1,B]"))--"rightMap(g)"--> PAABB(("P[A2,B2]"))
- Derived methods
def leftMap[A, B, C](fab: F[A, B])(f: A => C): F[C, B]
def rightMap[A, B, D](fab: F[A, B])(g: B => D): F[A, D]
def leftFunctor[X]: Functor[F[?, X]]
def rightFunctor[X]: Functor[F[X, ?]]
def umap[A, B](faa: F[A, A])(f: A => B): F[B, B]
def widen[A, B, C >: A, D >: B](fab: F[A, B]): F[C, D]
-
Implementations: Cats, Scalaz 7, Scalaz 8, Haskell, Purescript, Idris, Agda
-
Instances can be defined for: Tuple2, Either, Validated. For Function1 not - functions are contravariant for input type.
-
Resources
- Scalaz example
- Cats docs
- Funky Scala Bifunctor - Tony Morris (blog post)
- herding cats — Datatype-generic programming with Bifunctor (blog post (understand Free monads first))
- Haskell libraries using Bifunctors
- (Haskell) The Extended Functor Family - George Wilson: video
- (Haskell) Parametricity for Bifunctor - Brent Yorgey (blog post)
- https://twitter.com/xgrommx/status/1234493383126769665
Turn a Bifunctor with both arguments with the same type into Functor.
newtype Join p a = Join { runJoin :: p a a }
-- instance
-- Bifunctor p => Functor (Join p)
- Implementations Haskell bifunctors/Data.Bifunctor.Join japesinator/Idris-Bifunctors Data/Bifunctor.Join purescript-bifunctors/Data.Bifunctor.Join
Functor over second argument of a Bifunctor
newtype WrappedBifunctor p a b = WrapBifunctor { unwrapBifunctor :: p a b }
-- instance
-- Bifunctor p => Functor (WrappedBifunctor p a)
Swap arguments of Bifunctor
newtype Flip p a b = Flip { runFlip :: p b a }
-- instance
-- Bifunctor p => Bifunctor (Flip p)
Functor over second argument of Bifunctor
newtype Joker g a b = Joker { runJoker :: g b }
-- instance
-- Functor g => Bifunctor (Joker g :: * -> * -> *)
-- Functor g => Functor (Joker g a)
-
Implementations Haskell bifunctors/Data.Bifunctor.Joker
-
Resources
- Clowns to the Left of me, Jokers to the Right Dissecting Data Structures - Conor McBride (paper)
Functor over second argument of Bifunctor
newtype Clown f a b = Clown { runClown :: f a }
-- instances
-- Functor (Clown f a :: * -> *)
-- Functor f => Bifunctor (Clown f :: * -> * -> *)
-
Implementations Haskell bifunctors/Data.Bifunctor.Clown
-
Resources
- Clowns to the Left of me, Jokers to the Right Dissecting Data Structures - Conor McBride (paper)
Product of two Bifunctors
data Product f g a b = Pair (f a b) (g a b)
-- instance
-- (Bifunctor f, Bifunctor g) => Bifunctor (Product f g)
- Implementations Haskell bifunctors/Data.Bifunctor.Product purescript-bifunctors/Data.Bifunctor.Product
Sum of two Bifunctors
data Sum p q a b = L2 (p a b) | R2 (q a b)
-- instance
-- (Functor f, Bifunctor p) => Functor (Tannen f p a)
- Implementations Haskell bifunctors/Data.Bifunctor.Sum
Compose Functor on the outside.
newtype Tannen f p a b = Tannen { runTannen :: f (p a b) }
-- instances
-- (Functor f, Bifunctor p) => Bifunctor (Tannen f p)
-- (Functor f, Bifunctor p) => Functor (Tannen f p a)
- Implementations Haskell bifunctors/Data.Bifunctor.Tannen
Compose two Functors inside Bifunctor
newtype Biff p f g a b = Biff { runBiff :: p (f a) (g b) }
-- instance
-- (Bifunctor p, Functor f, Functor g) => Bifunctor (Biff p f g)
- Implementations: Haskell bifunctors/Data.Bifunctor.Biff
- Implementations: Scalaz Bitraverse Cats Bitraverse, Haskell, Purescript
- Implementations: (Scalaz Bifoldable) Cats Bifoldable Purescript
- Implementations: Haskell