Ответ 1
Arrows
обобщаются по категориям, поэтому класс Category
.
class Category f where
(.) :: f a b -> f b c -> f a c
id :: f a a
Определение типа Arrow
имеет Category
как суперкласс. Категории (в смысле haskell) обобщают функции (вы можете их компилировать, но не применять), и поэтому они определенно являются "моделью вычисления". Arrow
предоставляет Category
дополнительную структуру для работы с кортежами. Таким образом, в то время как Category
отражает что-то о функциональном пространстве Haskell, Arrow
расширяет его до типа продуктов.
Каждый Monad
порождает нечто, называемое "категорией Клейсли", и эта конструкция дает вам примеры ArrowApply
. Вы можете построить Monad
из любого ArrowApply
, чтобы полный круг не изменял ваше поведение, поэтому в некотором глубоком смысле Monad
и ArrowApply
- одно и то же.
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
id = Kleisli return
(Kleisli f) . (Kleisli g) = Kleisli (\b -> g b >>= f)
instance Monad m => Arrow (Kleisli m) where
arr f = Kleisli (return . f)
first (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d))
second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c))
На самом деле каждый Arrow
приводит к тому, что Applicative
(универсально квантифицированный для получения прав) в дополнение к суперклассу Category
, и я считаю, что сочетание соответствующих Category
и Applicative
достаточно для восстановления вашего Arrow
.
Итак, эти структуры глубоко связаны.
Предупреждение: впереди желаемый комментарий. Одно центральное различие между мышлением Functor
/Applicative
/Monad
и способом мышления Category
/Arrow
заключается в том, что while Functor
и его ilk являются обобщениями на уровне объекта (типы в Haskell), Category
/Arrow
являются генерализацией понятия морфизма (функции в Haskell). Я убежден, что мышление на уровне обобщенного морфизма связано с более высоким уровнем абстракции, чем мышление на уровне обобщенных объектов. Иногда это хорошо, иногда это не так. С другой стороны, несмотря на то, что Arrows
имеет категориальную основу, и никто из математиков не считает интересным Applicative
, я понимаю, что Applicative
в целом лучше понимается, чем Arrow
.
В принципе вы можете думать о "категории < Arrow < ArrowApply" и "Functor < Applicative < Monad", что "Category-Functor", "Arrow-Applicative" и "ArrowApply ~ Monad".
Больше бетона ниже: Что касается других структур для моделирования вычислений: часто можно перевернуть направление "стрелок" (только значения морфизмов здесь) в категориальных построениях, чтобы получить "двойное" или "совместное построение". Итак, если монада определена как
class Functor m => Monad m where
return :: a -> m a
join :: m (m a) -> m a
(хорошо, я знаю, что это не так, как Haskell определяет вещи, но ma >>= f = join $ fmap f ma
и join x = x >>= id
, так что это может быть и так)
то comonad
class Functor m => Comonad m where
extract :: m a -> a -- this is co-return
duplicate :: m a -> m (m a) -- this is co-join
Эта вещь оказывается довольно распространенной также. Оказывается, что Comonad
- основная базовая структура клеточных автоматов. Для полноты я должен отметить, что Эдвард Кметт Control.Comonad
помещает duplicate
в класс между функтором и Comonad
для "Расширяемые функторы", потому что вы также можете определить
extend :: (m a -> b) -> m a -> m b -- Looks familiar? this is just the dual of >>=
extend f = fmap f . duplicate
--this is enough
duplicate = extend id
Оказывается, что все Monad
также являются "расширяемыми"
monadDuplicate :: Monad m => m a -> m (m a)
monadDuplicate = return
тогда как все Comonads
являются "объединенными"
comonadJoin :: Comonad m => m (m a) -> m a
comonadJoin = extract
поэтому эти структуры очень близки друг к другу.