Po pierwsze:
Każda monada jest również funktorem aplikacyjnym, a każdy funktor aplikacyjny jest funktorem.
Jest to prawdą w kontekście Haskella, ale (czytając Applicative
jako „silny luźny funktor monoidalny”) nie ogólnie, z dość trywialnego powodu, że można mieć „aplikacyjne” funktory między różnymi kategoriami monoidalnymi, podczas gdy monady (i comonady) są endofunkorami .
Ponadto identyfikacja za Applicative
pomocą silnych luźnych funktorów monoidalnych jest nieco myląca, ponieważ uzasadnienie nazwy (i podpisu typu (<*>)
) wymaga funktora między zamkniętymi kategoriami monoidalnymi, który zachowuje zarówno strukturę monoidalną, jak i hom wewnętrzny . Można to prawdopodobnie nazwać „luźnym zamkniętym funktorem monoidalnym”, z tym wyjątkiem, że funktor między monoidalnymi zamkniętymi kategoriami, który zachowuje jedną właściwość, zachowuje drugą w oczywisty sposób . Ponieważ Applicative
opisuje tylko endofunkcje na Hask, zachowujące monoidalną strukturę (,)
, jego instancje automatycznie zyskują wiele właściwości, w tym ich wytrzymałość , którą można w ten sposób uniknąć.
Pozorny związek z tym Monad
jest prawdopodobnie artefaktem ukrytych ograniczeń Applicative
powodowania zbieżności aspektów ich odpowiednich struktur monoidów, szczęśliwy zbieg okoliczności, który niestety nie przeżywa dualizacji.
Tak jak comonada w kategorii jest monadą w C o p , tak opaksoryczny funktor monoidalny C → D jest luźnym funktorem monoidalnym C o p → D o p . Ale H a s k o p nie jest monoidalnie zamknięty , a nazwa ko- nie zawierająca aplikacji funkcji nie zasługuje na miano. W każdym razie wynik nie byłby wyjątkowo interesujący:CCop C→DCop→DopHaskopApplicative
class (Functor f) => CoMonoidal f where
counit :: f () -> ()
cozip :: f (a, b) -> (f a, f b)
Zamiast tego moglibyśmy wyobrazić sobie pojęcie „funktora zamkniętego colaxa”, który wyglądałby znacznie bardziej, Applicative
gdyby istniał. Niestety, wcale nie jest (według mojej najlepszej wiedzy) kategorią zamkniętą: w H a s k odpowiada morfizmom b → a w H a s k o p , ale nie działa jako wewnętrzny hom tam - ponieważ strzałki są odwrócone, zamiast tego wymagana byłaby jakaś funkcja, której nie możemy ogólnie zdefiniować dla H a s k .Haskopnewtype Op b a = Op (a -> b)
Haskb→aHaskopOp b a
Hask
Gdybyśmy po prostu udawali, że „funktory zamknięte colax” istniały dla , a ponadto działały w sposób, na jaki naiwnie mamy nadzieję, że tak się stanie, koegzystencja na tym prawdopodobnie wyglądałaby tak:HaskApplicative
class (Functor f) => CoApplicative f where
copure :: f a -> a
coap :: (f a -> f b) -> f (a -> b)
Oczywiście, dodanie duplicate :: f a -> f (f a)
do copure
tego spowodowałoby comonadę (zakładając, że prawa są spełnione). Ale nie ma oczywistego związku między - coap
czymkolwiek by to nie było - a extend :: (f a -> b) -> f a -> f b
. Porównując typy, staje się jasne, że dualizacja odbywa się na różne sposoby: struktury comonoidalne leżące u podstaw duplicate
i cozip
mające niewiele wspólnego ze sobą lub z nimi coap
(co prawdopodobnie i tak nie ma sensu), liftA2 (,)
a jednocześnie (<*>)
są równoważne i można je wyprowadzić join
.
Innym możliwym sposobem dualizacji Applicative
, który ma jeszcze mniej wspólnego z comonadami, jest rozważenie przeciwstawnych funktorów monoidalnych:
class (Contravariant f) => ContraMonoidal f where
contraunit :: f a
contrazip :: f a -> f b -> f (Either a b)
Działa to jednak z tymi samymi problemami, co powyżej, a mianowicie, że nie jest kategorią zamkniętą. Gdyby tak było, mielibyśmy jakiś rodzaj takie, że możemy napisać funkcje, jak i i tak dalej, że faktycznie pracował zgodnie z oczekiwaniami.Haskopb <~ a
contracurry :: (Either c b <~ a) -> (c <~ (b <~ a))
contraapply :: b -> Either a (a <~ b)
HaskCoApplicative
Jednak w monoidalnej zamkniętej kategorii, bardziej gościnnej dla dualizacji, możesz mieć więcej szczęścia. W szczególności uważam, że obie Kleisli (Cont r)
i ich przeciwna kategoria są zamknięte monoidalnie, więc może to być lepszy kontekst do zbadania tych pomysłów.