Методы Bifunctor vs. Arrow
Существует небольшое совпадение между Bifunctor
и Arrow
:
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')
Класс Bifunctor
поставляется с законами, полностью аналогичными законам Functor
.
Класс Arrow
поставляется с рядом законов, различных законов и несколько загадочным предупреждением о (***)
: "Обратите внимание, что это вообще не функтор". Удивительно (для меня) там только один закон о (***)
:
first f >>> arr (id *** g) = arr (id *** g) >>> first f
Экземпляр Arrow (->)
экземпляр Bifunctor (,)
точно совпадают, поэтому bimap @(,) = (***) @(->)
. Есть ли какое-то особое значение для этого? Есть ли значимые гипотетические
class Foo (~~>) p where
biFoo :: (a ~~> a') -> (b ~~> b') -> p a b ~~> p a' b'
Если так, это допускает функциональные зависимости?
Ответы
Ответ 1
Arrow
является (несколько ублюденным) предшественником класса декартовых замкнутых категорий или наименее декартовых моноидальных категорий. В частности, к моноидальным категориям, тензорное произведение которых (,)
и единичный элемент ()
.
Напомним, что моноидальная категория характеризуется тензорным произведением как бифунктором, поэтому существует ваша связь между Arrow
и Bifunctor
.
***
На самом деле законов больше, чем вы перечислили, только библиотека предпочитает формулировать их в терминах first
. Вот эквивалентное определение класса:
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
Кстати, также возможно удалить arr
для подъема чистых функций и вместо этого дать суперклассу только выделенные методы fst
, snd
и assoc
. Я называю этот класс Cartesian
. Это позволяет определять категории "стрелок", которые не содержат произвольных функций Haskell; линейные карты являются важным примером.
Ответ 2
Arrow
является эквивалентом для Strong
+ Category
.
Вы можете выбрать другое понятие силы, чтобы получить другой вид 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')
Другими словами, тензорное произведение вашей декартовой замкнутой категории не обязательно должно быть (,)
. Любой тензорный продукт, который вы можете придумать, имеет соответствующее понятие силы, каждое из которых даст вам соответствующее разнообразие Arrow
.
Примечательно, что многие профункторы являются как Strong
, так и Choice
, поэтому ваш Foo
(который в основном обобщает Strong
над тензорным произведением p
) не имеет функциональной зависимости.
К сожалению, модуль Control.Arrow
в base
немного запутывает иерархию (например, их ArrowChoice
имеет Arrow
в качестве суперкласса).