Kto pierwszy powiedział, co następuje?
Monada to po prostu monoid w kategorii endofunkorów, na czym polega problem?
A w mniej ważnej sprawie, czy to prawda, a jeśli tak, to czy mógłbyś podać wyjaśnienie (mam nadzieję, że może to być zrozumiałe dla kogoś, kto nie ma dużego doświadczenia Haskell)?
haskell
monads
category-theory
monoids
Roman A. Taycher
źródło
źródło
Odpowiedzi:
To szczególne sformułowanie autorstwa Jamesa Iry'ego z jego bardzo zabawnej Krótkiej, niekompletnej i głównie błędnej historii języków programowania , w której fikcyjnie przypisuje ją Philipowi Wadlerowi.
Oryginalny cytat pochodzi z Saunders Mac Lane w kategorii dla pracującego matematyka , jednego z podstawowych tekstów teorii kategorii. Tutaj jest w kontekście , który jest prawdopodobnie najlepszym miejscem, aby dowiedzieć się dokładnie, co to znaczy.
Ale wezmę dźgnięcie. Oryginalne zdanie jest następujące:
X tutaj jest kategorią. Endofunctors są funktory od kategorii do siebie (co jest zwykle wszystko
Functor
jest w miarę programiści funkcjonalne są zainteresowane, ponieważ są one głównie do czynienia z tylko jednej kategorii; kategorię rodzajów - ale to dygresja). Ale możesz sobie wyobrazić inną kategorię, którą jest kategoria „endofunkcji na X ”. Jest to kategoria, w której obiekty są endofunkcjami, a morfizmy są naturalnymi przemianami.A spośród tych endofunkorów niektóre z nich mogą być monadami. Które to monady? Dokładnie te, które są monoidalne w pewnym sensie. Zamiast dokładnego mapowania monad na monoidy (ponieważ Mac Lane robi to o wiele lepiej, niż mogłem się spodziewać), po prostu umieszczę ich odpowiednie definicje obok siebie i pozwolę ci porównać:
Monoid jest ...
... spełniając te prawa:
Monada to ...
* -> *
zFunctor
instancją)join
w Haskell)return
w Haskell)... spełniając te prawa:
Przy odrobinie mrużenia oka możesz zauważyć, że obie te definicje są instancjami tej samej abstrakcyjnej koncepcji .
źródło
S
jest typem, wszystko, co możesz zrobić, pisząc funkcję,f :: () -> S
to wybierz określony termin typuS
(„element”, jeśli chcesz) i wróć to ... nie otrzymałeś żadnej prawdziwej informacji z argumentem, więc nie ma sposobu, aby zmienić zachowanie funkcji.f
Musi więc istnieć stała funkcja, która za każdym razem zwraca to samo.()
(„Jednostka”) jest obiektem końcowym kategorii Hask i nie jest przypadkiem, że zamieszkuje ją dokładnie 1 (nie rozbieżna) wartość.Intuicyjnie myślę, że to, co mówi fantazyjne słownictwo matematyczne, brzmi:
Monoid
Monoid jest zbiorem obiektów i sposób ich łączenia. Dobrze znane monoidy to:
Istnieją również bardziej złożone przykłady.
Co więcej, każda monoid ma tożsamość , czyli ten element „no-op”, który nie działa, gdy połączysz go z czymś innym:
Wreszcie monoid musi być asocjacyjny . (możesz dowolnie skracać długi ciąg kombinacji, o ile nie zmienisz kolejności obiektów od lewej do prawej) Dodawanie jest OK ((5 + 3) +1 == 5+ (3+ 1)), ale odejmowanie nie jest ((5-3) -1! = 5- (3-1)).
Monada
Rozważmy teraz specjalny rodzaj zestawu i specjalny sposób łączenia obiektów.
Obiekty
Załóżmy, że Twój zestaw zawiera obiekty specjalnego rodzaju: funkcje . Funkcje te mają interesującą sygnaturę: nie przenoszą liczb na liczby ani ciągów znaków na ciągi znaków. Zamiast tego każda funkcja przenosi liczbę na listę liczb w dwuetapowym procesie.
Przykłady:
Łączenie obiektów
Ponadto nasz sposób łączenia funkcji jest wyjątkowy. Prostym sposobem na połączenie funkcji jest kompozycja : weźmy nasze przykłady powyżej i ułóż każdą funkcję ze sobą:
Nie wnikając zbytnio w teorię typów, chodzi o to, że można połączyć dwie liczby całkowite, aby uzyskać liczbę całkowitą, ale nie zawsze można skomponować dwie funkcje i uzyskać funkcję tego samego typu. (Funkcje typu a -> a utworzą, ale a-> [a] nie.)
Zdefiniujmy więc inny sposób łączenia funkcji. Łącząc dwie z tych funkcji, nie chcemy „podwójnie owijać” wyników.
Oto co robimy. Kiedy chcemy połączyć dwie funkcje F i G, postępujemy zgodnie z tym procesem (zwanym wiązaniem ):
Wracając do naszych przykładów, połączmy (powiązaj) funkcję ze sobą, korzystając z tego nowego sposobu „wiązania” funkcji:
Ten bardziej wyrafinowany sposób łączenia funkcji jest asocjacyjny ( wynika z tego, jak kompozycja funkcji jest asocjacyjna, gdy nie robisz fantazyjnych rzeczy do pakowania).
Łącząc to wszystko razem,
Notatki
Istnieje wiele sposobów „zawijania” wyników. Możesz utworzyć listę, zestaw lub odrzucić wszystkie wyniki oprócz pierwszego, zwracając uwagę, czy nie ma żadnych wyników, dołączyć boczny stan, wydrukować komunikat dziennika itp.
Grałem trochę luźno z definicjami w nadziei, że intuicyjnie zrozumiem niezbędny pomysł.
Uprościłem trochę, nalegając, aby nasza monada działała na funkcjach typu a -> [a] . W rzeczywistości monady działają na funkcjach typu a -> mb , ale uogólnienie jest rodzajem detalu technicznego, który nie jest głównym wglądem.
źródło
a -> [b]
ic -> [d]
(możesz to zrobić tylko, jeślib
=c
), to nie całkiem opisuje monoid. Jest to tak naprawdę opisana operacja spłaszczania, a nie kompozycja funkcji, która jest „operatorem monoidu”.a -> [a]
, będzie to monoid (ponieważ ograniczysz kategorię Kleisli do jednego obiektu i dowolnej kategorii tylko jednego obiektu jest z definicji monoidem!), ale nie uchwyciłby pełnej ogólności monady.Po pierwsze, rozszerzenia i biblioteki, których będziemy używać:
Spośród nich
RankNTypes
jest jedynym, który jest absolutnie niezbędny dla poniższych. Kiedyś napisałem wyjaśnienieRankNTypes
że niektórzy ludzie wydają się przydatni , więc odniosę się do tego.Cytując doskonałą odpowiedź Toma Crocketta , mamy:
Jak tłumaczymy to na kod Haskell? Zacznijmy od pojęcia naturalnej transformacji :
Typ formy
f :-> g
jest analogiczny do typu funkcji, ale zamiast myśleć o nim jako o funkcji między dwoma typami (rodzaju*
), pomyśl o nim jako o morfizmie między dwoma funktorami (każdy z rodzaju* -> *
). Przykłady:Zasadniczo w Haskell transformacje naturalne są funkcjami z
f x
jednego typu do innego typu,g x
tak żex
zmienna typu jest „niedostępna” dla dzwoniącego. Na przykładsort :: Ord a => [a] -> [a]
nie można go przekształcić w naturalną transformację, ponieważ jest „wybredna”, co do tego, dla jakich typów możemy utworzyć instancjęa
. Jednym z intuicyjnych sposobów myślenia o tym jest:Teraz, nie wspominając o tym, zajmiemy się klauzulami definicji.
Pierwsza klauzula to „endofunkcja, T: X -> X ”. Cóż, każdy
Functor
w Haskell jest endofunkorem w tym, co ludzie nazywają „kategorią Hask”, którego obiektami są rodzaje Haskell (swego rodzaju*
), a morfizmy są funkcjami Haskella. To brzmi jak skomplikowane stwierdzenie, ale w rzeczywistości jest bardzo trywialne. Oznacza to tylko, że aFunctor f :: * -> *
daje środki do konstruowania typuf a :: *
dla dowolneja :: *
i funkcjifmap f :: f a -> f b
z dowolnejf :: a -> b
, i że są one zgodne z prawami funktorów.Druga klauzula:
Identity
funktor w Haskell (który jest dostarczany z platformą, więc możesz go po prostu zaimportować) jest zdefiniowany w następujący sposób:Tak więc naturalną transformację η: I -> T z definicji Toma Crocketta można zapisać w ten sposób dla każdego
Monad
przypadkut
:Trzecia klauzula: Skład dwóch funktorów w Haskell można zdefiniować w ten sposób (który również jest dostarczany z platformą):
Tak więc naturalną transformację μ: T × T -> T z definicji Toma Crocketta można zapisać w następujący sposób:
Stwierdzenie, że jest to monoid w kategorii endofunkcji, oznacza zatem, że
Compose
(częściowo zastosowane tylko do jego pierwszych dwóch parametrów) jest asocjacyjne i toIdentity
jest jego element tożsamości. Tzn., Że posiadają następujące izomorfizmy:Compose f (Compose g h) ~= Compose (Compose f g) h
Compose f Identity ~= f
Compose Identity g ~= g
Są one bardzo łatwe do udowodnienia, ponieważ
Compose
iIdentity
oba są zdefiniowane jakonewtype
, a Raporty Haskella definiują semantykęnewtype
jako izomorfizm między definiowanym typem a rodzajem argumentunewtype
konstruktora danych. Na przykład udowodnijmyCompose f Identity ~= f
:źródło
Natural
nowym typie nie mogę zrozumieć, co(Functor f, Functor g)
robi ograniczenie. Czy możesz wytłumaczyć?Functor
ograniczenia, ponieważ nie wydają się konieczne. Jeśli się nie zgadzasz, możesz je dodać ponownie.join
jest zdefiniowany. I tojoin
jest morfizm projekcyjny. Ale nie jestem pewien.Uwaga: Nie, to nie jest prawda. W pewnym momencie do tej odpowiedzi skomentował sam Dan Piponi, mówiąc, że przyczyną i skutkiem jest dokładnie odwrotnie, że napisał swój artykuł w odpowiedzi na żart Jamesa Iry'ego. Ale wydaje się, że został usunięty, być może przez jakiś kompulsywny porządek.
Poniżej moja oryginalna odpowiedź.
Całkiem możliwe, że Iry przeczytał From Monoids to Monads , post, w którym Dan Piponi (sigfpe) wywodzi monady z monoidów w Haskell, z dużą dyskusją na temat teorii kategorii i wyraźną wzmianką o „kategorii endofunkorów na Hask ”. W każdym razie każdy, kto zastanawia się, co to znaczy, że monada jest monoidem w kategorii endofunkcji, może skorzystać z lektury tego wyprowadzenia.
źródło
:-)
.Doszedłem do tego postu, aby lepiej zrozumieć wniosek o niesławnym cytacie z teorii kategorii Mac Lane'a dla pracującego matematyka .
Opisując, czym jest, często równie przydatne jest opisanie tego, czym nie jest.
Fakt, że Mac Lane używa opisu do opisu Monady, może sugerować, że opisuje on coś unikalnego dla Monad. Zrób ze mną. Aby rozwinąć szersze zrozumienie tego oświadczenia, uważam, że należy wyjaśnić, że tak jest nie opisuje on czegoś, co jest unikalne dla monad; oświadczenie w równym stopniu opisuje między innymi aplikację i strzałki. Z tego samego powodu możemy mieć dwa monoidy na Int (suma i iloczyn), możemy mieć kilka monoidów na X w kategorii endofunkcji. Ale podobieństw jest jeszcze więcej.
Zarówno Monad, jak i Wnioskodawca spełniają kryteria:
(np. z dnia na dzień
Tree a -> List b
, ale w kategoriiTree -> List
)Tree -> List
tylkoList -> List
.W instrukcji użyto „Kategoria ...”. Definiuje zakres instrukcji. Jako przykład, funktor Kategoria opisuje zakres
f * -> g *
, czyliAny functor -> Any functor
npTree * -> List *
lubTree * -> Tree *
.To, czego nie określa stwierdzenie kategoryczne, opisuje, gdzie wszystko i wszystko jest dozwolone .
W tym przypadku wewnątrz funktorów
* -> *
akaa -> b
nie jest określona, co oznaczaAnything -> Anything including Anything else
. Gdy moja wyobraźnia przeskakuje do Int -> String, zawiera takżeInteger -> Maybe Int
, a nawetMaybe Double -> Either String Int
gdziea :: Maybe Double; b :: Either String Int
.Zatem zestawienie składa się w następujący sposób:
:: f a -> g b
(tj. dowolny sparametryzowany typ do dowolnego sparametryzowanego typu):: f a -> f b
(tj. dowolny sparametryzowany typ do tego samego sparametryzowanego typu) ... powiedział inaczej,Gdzie zatem jest moc tego konstruktu? Aby docenić pełną dynamikę, musiałem zobaczyć, że typowe rysunki monoidu (pojedynczy obiekt z czymś, co wygląda jak strzałka tożsamości
:: single object -> single object
), nie pokazują, że wolno mi użyć strzałki sparametryzowanej z dowolną liczbą wartości monoidów, z obiektu jednego typu dozwolonego w Monoid. Definicja równoważności strzałki endo ~ ~ ignoruje wartość typu funktora, a także rodzaj i wartość najbardziej wewnętrznej warstwy „ładunku”. Zatem równoważność zwraca siętrue
w każdej sytuacji, w której typy funktorów pasują do siebie (np.Nothing -> Just * -> Nothing
Jest równoważne,Just * -> Just * -> Just *
ponieważ oba są obaMaybe -> Maybe -> Maybe
).Pasek boczny: ~ na zewnątrz jest koncepcyjny, ale jest najbardziej lewym symbolem w
f a
. Opisuje również, co „Haskell” wczytuje jako pierwsze (duży obrazek); więc typ jest „zewnętrzny” w stosunku do wartości typu. Relacja między warstwami (łańcuchem odniesień) w programowaniu nie jest łatwa do odniesienia w kategorii. Kategoria zestawu służy do opisywania typów (Int, Strings, Może Int itp.), Która obejmuje kategorię Functor (typy sparametryzowane). Łańcuch referencyjny: Typ Functora, Wartości Functora (elementy zestawu Functora, np. Nic, Tylko), a z kolei wszystko inne, na co wskazuje każda wartość funktora. W kategorii związek jest opisany inaczej, np.return :: a -> m a
Jest uważany za naturalną transformację z jednego Functora na inny Functor, różny od wszystkiego, co wspomniano do tej pory.Wracając do głównego wątku, w sumie, dla dowolnego zdefiniowanego produktu tensorowego i wartości neutralnej, stwierdzenie kończy się niesamowicie potężnym konstruktem obliczeniowym zrodzonym z jego paradoksalnej struktury:
:: List
); statycznyfold
nie mówi nic o ładunku)W Haskell wyjaśnienie stosowalności oświadczenia jest ważne. Moc i wszechstronność tego konstruktu nie ma absolutnie nic wspólnego z monadą samą w sobie . Innymi słowy, konstrukcja nie opiera się na tym, co czyni monadę wyjątkową.
Próbując dowiedzieć się, czy zbudować kod w kontekście współużytkowanym w celu obsługi obliczeń, które są od siebie zależne, w porównaniu z obliczeniami, które można uruchamiać równolegle, ta niesławna instrukcja, o ile opisuje, nie stanowi kontrastu między wyborem Zastosowanie, strzały i monady, ale raczej opis, jak bardzo są takie same. W przypadku podjętej decyzji stwierdzenie jest dyskusyjne.
Jest to często źle rozumiane. Stwierdzenie to opisuje dalej
join :: m (m a) -> m a
jako iloczyn tensorowy monoidalnego endofunkcji. Nie precyzuje jednak, jak w kontekście tego oświadczenia(<*>)
można było również wybrać. To naprawdę przykład sześciu / pół tuzina. Logika łączenia wartości jest dokładnie taka sama; te same dane wejściowe generują takie same dane wyjściowe z każdego z nich (w przeciwieństwie do monoidów Sum i Produkt dla Int, ponieważ generują różne wyniki podczas łączenia Ints).Podsumowując: monoid w kategorii endofunkcji opisuje:
(<*>)
i(>>=)
oba zapewniają jednoczesny dostęp do dwóchm
wartości w celu obliczenia pojedynczej wartości zwracanej. Logika zastosowana do obliczenia wartości zwracanej jest dokładnie taka sama. Gdyby nie to, dla różnych kształtów funkcji ich parametryzacji (f :: a -> b
kontrak :: a -> m b
) oraz pozycji parametru tego samego typu zwracanej obliczeń (tj,a -> b -> b
w porównaniub -> a -> b
do siebie odpowiednio), podejrzewam, że nie mogliśmy zostać sparametryzowane w monoidal cyfrowym, produkt tensorowy do ponownego użycia w obu definicjach. Jako ćwiczenie, które ma sens, spróbuj wdrożyć~t
, a skończysz na tym(<*>)
iw(>>=)
zależności od tego, jak zdecydujesz się go zdefiniowaćforall a b
.Jeśli mój ostatni punkt jest przynajmniej koncepcyjnie prawdziwy, to wyjaśnia dokładną i jedyną różnicę obliczeniową między Applicative i Monad: funkcje, które sparametryzują. Innymi słowy, różnica jest zewnętrzna w stosunku do implementacji tych klas typów.
Podsumowując, z własnego doświadczenia, niesławny cytat Mac Lane'a dostarczył świetnego memu „goto”, który był dla mnie drogowskazem podczas nawigacji przez kategorię, aby lepiej zrozumieć idiomy używane w Haskell. Udało mu się uchwycić zakres potężnej mocy obliczeniowej, doskonale dostępnej w Haskell.
Jednak ironia polega na tym, że po raz pierwszy źle zrozumiałem zastosowanie tego oświadczenia poza monadą i to, co mam nadzieję tu przekazać. Wszystko, co opisuje, okazuje się być podobne między Applicative a Monadami (i innymi strzałkami). To, czego nie mówi, to dokładnie małe, ale użyteczne rozróżnienie między nimi.
- E
źródło
Odpowiedzi tutaj doskonale sprawdzają się w definiowaniu zarówno monoidów, jak i monad, jednak nadal nie wydają się odpowiadać na pytanie:
Sednem materii, której tu brakuje, jest odmienne pojęcie „monoidu”, a dokładniej tak zwana kategoryzacja - pojęcie monoidu w kategorii monoidalnej. Niestety sama książka Mac Lane sprawia, że jest bardzo myląca :
Główne zamieszanie
Dlaczego to jest mylące? Ponieważ nie definiuje, co jest „monoidem w kategorii endofunkcji”
X
. Zamiast tego zdanie to sugeruje umieszczenie monoidu w zbiorze wszystkich endofunkorów wraz z kompozycją funktorów jako operacją binarną i funktorem tożsamości jako jednostką monoidalną. Który działa idealnie dobrze i zamienia w monoid dowolny podzbiór endofunkorów, który zawiera funktor tożsamości i jest zamknięty w składzie funktora.Nie jest to jednak poprawna interpretacja, której książka nie wyjaśnia na tym etapie. Monada
f
jest stałym endofunktorem, a nie podzbiorem endofunkorów zamkniętych w składzie. Wspólna konstrukcja jest użycief
do wytworzenia się monoid poprzez zbiór wszystkichk
krotnie kompozycjif^k = f(f(...))
of
z, w tym,k=0
który odpowiada tożsamośćf^0 = id
. A teraz zestawS
wszystkich tych mocy dla wszystkichk>=0
jest rzeczywiście monoidem „z iloczynem × zastąpionym przez skład endofunkcji i zestawem jednostek przez endofunkcję tożsamości”.I jeszcze:
S
można zdefiniować dla dowolnego funktoraf
lub nawet dosłownie dla dowolnej mapy własnejX
. Jest to monoid wygenerowany przezf
.S
podana przez skład funktora i funktor tożsamości nie ma nic wspólnego zf
byciem lub nie byciem monadą.Aby jeszcze bardziej skomplikować sprawę, definicja „monoidu w kategorii monoidalnej” znajduje się w dalszej części książki, jak widać ze spisu treści . A jednak zrozumienie tego pojęcia jest absolutnie niezbędne do zrozumienia związku z monadami.
(Surowe) kategorie monoidalne
Idąc do rozdziału VII na Monoids (co przychodzi później niż rozdziale VI na monady), znajdujemy definicję tzw ścisłym kategorii monoidal jako potrójny
(B, *, e)
, gdzieB
jest to kategoria,*: B x B-> B
do bifunctor (funktor w odniesieniu do każdego składnika z drugiej składowej stałej ) ie
jest przedmiotem jednostkowymB
, spełniającym zasady asocjatywności i jednostkowe:dla dowolnych obiektów
a,b,c
zB
, a tym samym tożsamości dla wszelkich morfizmówa,b,c
ze
wyrazamiid_e
, z morfizmu tożsamoście
. Pouczające jest teraz obserwowanie, że w naszym interesującym przypadku, gdzieB
jest kategoria endofunkcjiX
z naturalnymi przekształceniami jak morfizmy,*
skład funktora ie
funktor tożsamości, wszystkie te prawa są spełnione, co można bezpośrednio zweryfikować.W książce pojawia się definicja „zrelaksowanej” kategorii monoidalnej , w której prawa utrzymują jedynie modulo pewne ustalone naturalne transformacje spełniające tak zwane relacje koherencji , co jednak nie jest ważne w naszych przypadkach kategorii endofunkcjonalnych.
Monoidy w kategoriach monoidalnych
Wreszcie, w sekcji 3 „Monoidy” rozdziału VII podano rzeczywistą definicję:
dzięki czemu 3 diagramy są przemienne. Przypomnijmy, że w naszym przypadku są to morfizmy w kategorii endofunkcji, które są naturalnymi przekształceniami odpowiadającymi precyzyjnie
join
ireturn
monadzie. Połączenie staje się jeszcze wyraźniejsze, gdy uwydatniamy kompozycję*
, zastępującc * c
jąc^2
, gdziec
jest nasza monada.Na koniec zauważ, że 3 diagramy przemienne (w definicji monoidu w kategorii monoidalnej) są napisane dla ogólnych (nie ścisłych) kategorii monoidalnych, podczas gdy w naszym przypadku wszystkie naturalne transformacje powstałe w ramach kategorii monoidalnej są w rzeczywistości tożsamościami. To sprawi, że diagramy będą dokładnie takie same jak te w definicji monady, dzięki czemu korespondencja będzie kompletna.
Wniosek
Podsumowując, każda monada z definicji jest endofunkcją, a zatem przedmiotem w kategorii endofunkcji, gdzie monady
join
ireturn
operatory spełniają definicję monoidu w tej konkretnej (ścisłej) monoidalnej kategorii . Odwrotnie, każda monoid w monoidalnej kategorii endofunkorów jest z definicji potrójnym(c, mu, nu)
składającym się z obiektu i dwóch strzałek, np. Naturalnych przekształceń w naszym przypadku, spełniających te same prawa co monada.Na koniec zwróć uwagę na kluczową różnicę między (klasycznymi) monoidami a bardziej ogólnymi monoidami w kategoriach monoidalnych. Dwie strzałki
mu
inu
powyżej nie są już operacją binarną i jednostką w zestawie. Zamiast tego masz jeden stały endofunkorc
. Sam skład funktora*
i funktor tożsamości nie zapewniają pełnej struktury potrzebnej monadzie, pomimo tej mylącej uwagi w książce.Innym rozwiązaniem byłoby porównać ze standardowym monoid
C
wszystkich samorządów map zestawuA
, gdzie operacja binarna jest skład, który można zobaczyć na mapę standardowy iloczyn kartezjańskiC x C
doC
. Przechodząc do sklasyfikowanego monoidu, zastępujemy iloczyn kartezjańskix
składem funktora*
, a operację binarną zastępuje naturalna transformacjamu
zc * c
nac
, czyli zbiórjoin
operatorówdla każdego obiektu
T
(wpisz programowanie). A elementy tożsamości w klasycznych monoidach, które można zidentyfikować za pomocą obrazów map ze stałego zestawu jednopunktowego, zostaną zastąpione kolekcjąreturn
operatorówAle teraz nie ma już produktów kartezjańskich, więc nie ma par elementów, a zatem nie ma operacji binarnych.
źródło