There is a bit of overlap between Bifunctor
and Arrow
methods:
class Bifunctor p where
first :: (a -> a') -> p a b -> p a' b
second :: (b -> b') -> p a b -> p a b'
bimap :: (a -> a') -> (b -> b') -> p a b -> p a' b'
class Arrow (~~>) where
...
first :: (a ~~> a') -> (a, b) ~~> (a', b)
second :: (b ~~> b') -> (a, b) ~~> (a, b')
(***) :: (a ~~> a') -> (b ~~> b') -> (a, b) ~~> (a', b')
The Bifunctor
class comes with laws completely analogous to those of Functor
.
The Arrow
class comes with a number of laws different laws and a somewhat cryptic warning about (***)
: "Note that this is in general not a functor." Surprisingly (to me) there's only one law about (***)
:
first f >>> arr (id *** g) = arr (id *** g) >>> first f
The Arrow (->)
instance and the Bifunctor (,)
instance match up exactly, so that bimap @(,) = (***) @(->)
. Is there some special significance to this? Is there a meaningful hypothetical
class Foo (~~>) p where
biFoo :: (a ~~> a') -> (b ~~> b') -> p a b ~~> p a' b'
If so, does that admit functional dependencies?
Arrow
is a (somewhat bastardized) precursor to a class of cartesian closed categories, or a least cartesian monoidal categories. Specifically, to monoidal categories whose tensor product is (,)
and unit element ()
.
Recall that a monoidal category is characterised by the tensor product as a bifunctor, so there's your connection between Arrow
and Bifunctor
.
***
has in fact more laws than you listed, only, the library chooses to formulate those in terms of first
instead. Here's an equivalent definition of the class:
class (Category k, Category k') => EnhancedCategory k k' where
arr :: k a b -> k' a b
-- arr id ≡ id
-- arr (f . g) = arr f . arr g
class (EnhancedCategory (->) a) => Arrow a where
(***) :: a b c -> a b' c' -> a (b,b') (c,c')
-- (f***id) . (g***id) ≡ (f.g)***id
-- (id***f) . (id***g) ≡ id***(f.g)
-- arr fst . (f***id) ≡ f . arr fst
-- arr snd . (id***g) ≡ g . arr snd
-- ¿ arr swap . (f***g) ≡ (g***f) . arr swap ?
-- ((f***g)***h) . assoc ≡ assoc . (f***(g***h))
diag :: a b (b,b)
first :: Arrow a => a b c -> a (b,d) (c,d)
first f = f***id
second :: Arrow a => a b c -> a (d,b) (d,c)
second g = id***g
(&&&) :: Arrow a => a b c -> a b d -> a b (c,d)
f&&&g = (f***g) . diag
Incidentally, it is also possible to remove the arr
for lifting pure functions, and instead give the superclass only the dedicated methods fst
, snd
and assoc
. I call that class Cartesian
. This allows defining “arrow” categories that don't contain arbitrary Haskell functions; linear maps are an important example.
Arrow
is equivalent to Strong
+ Category
.
You can choose a different notion of strength to get a different kind of Arrow
.
class Category a => ArrowChoice a where
arr :: (b -> c) -> a b c
(+++) :: a b c -> a b' c' -> a (Either b b') (Either c c')
In other words, the tensor product of your Cartesian closed category needn't be (,)
exactly. Any tensor product you can come up with has a corresponding notion of strength, each of which would give you a corresponding variety of Arrow
.
Notably, many profunctors are both Strong
and Choice
, so your Foo
(which basically generalises Strong
over a tensor product p
) doesn't have a functional dependency.
The Control.Arrow
module in base
unfortunately muddles the hierarchy together a little bit (for example, their ArrowChoice
has Arrow
as a superclass).
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