12

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?

duplode
  • 31,361
  • 7
  • 69
  • 130
dfeuer
  • 44,398
  • 3
  • 56
  • 155

2 Answers2

6

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.

leftaroundabout
  • 101,764
  • 3
  • 156
  • 291
  • There's lots I don't understand yet, but let me start by asking what `EnhancedCategory` is. – dfeuer Jul 12 '19 at 20:06
  • 1
    `class EnhancedCategory k k' where arr :: k a b -> k' a b`. Actually I'm not sure if the term is established or I made it up new [in `constrained-categories`](http://hackage.haskell.org/package/constrained-categories-0.4.0.0/docs/Control-Arrow-Constrained.html#t:EnhancedCat). — Edited that into answer. – leftaroundabout Jul 12 '19 at 20:15
  • `arr fst . (f***g) ≡ f . arr fst` that (and the two after it) doesn't seem right for the `Kleisli m` arrow instance. – Li-yao Xia Jul 12 '19 at 21:04
  • Hm, you're right; this actually seems specific to `f***id` (i.e., to `first f`). Also, not sure about the swap law... – leftaroundabout Jul 12 '19 at 21:35
  • So I guess there *could* be a `MonoidalCategory` class with an explicit tensor product. But no fundeps? – dfeuer Jul 13 '19 at 11:41
  • You mean, like http://hackage.haskell.org/package/categories-1.0.7/docs/Control-Category-Cartesian.html ? – leftaroundabout Jul 13 '19 at 22:31
  • Is `EnhancedCategory` not just a different name for `Functor` (generalised to non-**Hask**)? – Benjamin Hodgson Jul 15 '19 at 09:52
  • @BenjaminHodgson yeah, it's kind of “two categories with one canonical functor between them”. If there's a name for that in maths, I'd like to know. `Functor` at any rate can't be used for that class... – leftaroundabout Jul 15 '19 at 10:01
1

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).

Benjamin Hodgson
  • 37,496
  • 16
  • 98
  • 147