Haskell's Data.Bifunctor
is basically:
class Bifunctor f where bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
I could find a Biapply
as well. My question is, why isn't there a complete bi-hierarchy (bierarchy?) like:
class Bifunctor f => Biapplicative f where bipure :: a -> b -> f a b biap :: f (a -> b) (c -> d) -> f a c -> f b d class Biapplicative m => Bimonad m where bibind :: m a b -> (a -> b -> m c d) -> m c d bireturn :: a -> b -> m a b bireturn = bipure bilift :: Biapplicative f => (a -> b) -> (c -> d) -> f a c -> f b d bilift f g = biap $ bipure f g bilift2 :: Biapplicative f => (a -> b -> c) -> (x -> y -> z) -> f a x -> f b y -> f c z bilift2 f g = biap . biap (bipure f g)
Pair is an instance of these:
instance Bifunctor (,) where bimap f g (x,y) = (f x, g y) instance Biapplicative (,) where bipure x y = (x,y) biap (f,g) (x,y) = (f x, g y) instance Bimonad (,) where bibind (x,y) f = f x y
And types like...
data Maybe2 a b = Fst a | Snd b | None --or data Or a b = Both a b | This a | That b | Nope
...would IMO have instances as well.
Are there not enough matching types? Or is something concerning my code deeply flawed?
A monad in category theory is an endofunctor, i.e. a functor where the domain and codomain is the same category. But a Bifunctor
is a functor from the product category Hask x Hask
to Hask
. But we could try to find out what a monad in the Hask x Hask
category looks like. It is a category where objects are pairs of types, i.e. (a, b)
, and arrows are pairs of functions, i.e. an arrow from (a, b)
to (c, d)
has type (a -> c, b -> d)
. An endofunctor in this category maps pairs of types to pairs of types, i.e. (a, b)
to (l a b, r a b)
, and pairs of arrows to pairs of arrows, i.e.
(a -> c, b -> d) -> (l a b -> l c d, r a b -> r c d)
If you split this map function in 2, you'll see that an endofunctor in Hask x Hask
is the same as two Bifunctor
s, l
and r
.
Now for the monad: return
and join
are arrows, so in this case both are 2 functions. return
is an arrow from (a, b)
to (l a b, r a b)
, and join
is an arrow from (l (l a b) (r a b), r (l a b) (r a b))
to (l a b, r a b)
. This is what it looks like:
class (Bifunctor l, Bifunctor r) => Bimonad l r where bireturn :: (a -> l a b, b -> r a b) bijoin :: (l (l a b) (r a b) -> l a b, r (l a b) (r a b) -> r a b)
Or separated out:
class (Bifunctor l, Bifunctor r) => Bimonad l r where bireturnl :: a -> l a b bireturnr :: b -> r a b bijoinl :: l (l a b) (r a b) -> l a b bijoinr :: r (l a b) (r a b) -> r a b
And similar to m >>= f = join (fmap f m)
we can define:
bibindl :: l a b -> (a -> l c d) -> (b -> r c d) -> l c d bibindl lab l r = bijoinl (bimap l r lab) bibindr :: r a b -> (a -> l c d) -> (b -> r c d) -> r c d bibindr rab l r = bijoinr (bimap l r rab)
Recently, relative monads have been developed. A relative monad doesn't need to be an endofunctor! If we translate from the paper to Bifunctor
s in Haskell, you get:
class RelativeBimonad j m where bireturn :: j a b -> m a b bibind :: m a b -> (j a b -> m c d) -> m c d
Which defines a monad relative to the bifunctor j
. If you pick j
to be (,)
you get your definition.
The laws are the same as the monad laws:
bireturn jab `bibind` k = k jab m `bibind` bireturn = m m `bibind` (\jab -> k jab `bibind` h) = (m `bibind` k) `bibind` h
The first law prevents Maybe2
from being an instance, because bibind
has to be able to extract both values from the result of bireturn
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With