Po krótkim spojrzeniu na Haskella, jakie byłoby krótkie, zwięzłe i praktyczne wyjaśnienie, czym właściwie jest monada?
Odkryłem, że większość wyjaśnień jest dość niedostępna i pozbawiona praktycznych szczegółów.
haskell
functional-programming
monads
terminology
Peter Mortensen
źródło
źródło
Odpowiedzi:
Po pierwsze: termin monada jest trochę niepotrzebny, jeśli nie jesteś matematykiem. Alternatywnym terminem jest konstruktor obliczeń, który jest nieco bardziej opisowy w stosunku do tego, do czego są faktycznie przydatni.
Pytasz o praktyczne przykłady:
Przykład 1: Zrozumienie listy :
To wyrażenie zwraca podwojenie wszystkich liczb nieparzystych w zakresie od 1 do 10. Bardzo przydatne!
Okazuje się, że tak naprawdę jest to po prostu cukier składniowy dla niektórych operacji w monadzie List. To samo rozumienie listy można zapisać jako:
Lub nawet:
Przykład 2: Wejście / Wyjście :
Oba przykłady używają monad, konstruktorów obliczeń AKA. Częstym tematem jest to, że monada łączy operacje w określony, użyteczny sposób. W rozumieniu listy operacje są powiązane w taki sposób, że jeśli operacja zwraca listę, wówczas dla każdej pozycji na liście wykonywane są następujące operacje . Z drugiej strony monada IO wykonuje operacje sekwencyjnie, ale przekazuje „ukrytą zmienną”, która reprezentuje „stan świata”, co pozwala nam pisać kod I / O w sposób czysto funkcjonalny.
Okazuje się, że schemat operacji łańcuchowych jest dość przydatny i jest używany do wielu różnych rzeczy w Haskell.
Innym przykładem są wyjątki: przy użyciu
Error
monady operacje są powiązane w taki sposób, że są wykonywane sekwencyjnie, z wyjątkiem sytuacji, gdy zostanie zgłoszony błąd, w którym to przypadku reszta łańcucha zostanie porzucona.Zarówno składnia ze zrozumieniem listy, jak i notacja są składniowym cukrem do operacji łączenia za pomocą
>>=
operatora. Monada jest w zasadzie tylko typem obsługującym>>=
operatora.Przykład 3: Analizator składni
Jest to bardzo prosty parser, który analizuje albo cytowany ciąg, albo liczbę:
Operacje
char
,digit
itp są dość proste. One pasują albo nie pasują. Magią jest monada, która zarządza przepływem sterowania: operacje są wykonywane sekwencyjnie, dopóki dopasowanie się nie powiedzie, w którym to przypadku monada cofa się do najnowszej<|>
i próbuje następnej opcji. Ponownie, sposób łączenia operacji z pewną dodatkową, przydatną semantyką.Przykład 4: Programowanie asynchroniczne
Powyższe przykłady są w Haskell, ale okazuje się, że F # obsługuje również monady. Ten przykład został skradziony z Don Syme :
Ta metoda pobiera stronę internetową. Używana jest linia dziurkowania
GetResponseAsync
- faktycznie czeka ona na odpowiedź w oddzielnym wątku, podczas gdy główny wątek wraca z funkcji. Ostatnie trzy wiersze są wykonywane w odrodzonym wątku po otrzymaniu odpowiedzi.W większości innych języków musisz jawnie utworzyć osobną funkcję dla linii obsługujących odpowiedź.
async
Monada jest w stanie „Split” bloku na własną rękę i odroczyć wykonanie drugiej połowie. (async {}
Składnia wskazuje, że przepływ kontrolny w bloku jest definiowany przezasync
monadę.)Jak oni pracują
Jak więc monada może robić te wszystkie wymyślne rzeczy z kontrolą przepływu? To, co faktycznie dzieje się w bloku do (lub wyrażeniu obliczeniowym, jak są one wywoływane w F #), polega na tym, że każda operacja (w zasadzie każda linia) jest zawinięta w osobną anonimową funkcję. Funkcje te są następnie łączone za pomocą
bind
operatora (pisane>>=
w języku Haskell). Ponieważbind
operacja łączy funkcje, może wykonywać je według własnego uznania: sekwencyjnie, wielokrotnie, odwrotnie, odrzucać niektóre, wykonywać niektóre w osobnym wątku, gdy ma na to ochotę i tak dalej.Na przykład jest to rozszerzona wersja kodu IO z przykładu 2:
To brzydsze, ale bardziej oczywiste, co się właściwie dzieje.
>>=
Operator jest magiczny składnik: Potrzeba wartość (po lewej stronie) i łączy go z funkcji (po prawej stronie), aby wytworzyć nową wartość. Ta nowa wartość jest następnie przejmowana przez następnego>>=
operatora i ponownie łączona z funkcją tworzenia nowej wartości.>>=
może być postrzegany jako mini-ewaluator.Zauważ, że
>>=
jest przeciążony dla różnych typów, więc każda monada ma własną implementację>>=
. (Jednak wszystkie operacje w łańcuchu muszą być tego samego rodzaju monady, w przeciwnym razie>>=
operator nie będzie działał).Najprostsza możliwa implementacja po
>>=
prostu bierze wartość po lewej stronie i stosuje ją do funkcji po prawej stronie i zwraca wynik, ale jak powiedziano wcześniej, to, co sprawia, że cały wzór jest użyteczny, to gdy dzieje się coś dodatkowego w implementacji monady>>=
.Istnieje pewna sprytność w przekazywaniu wartości z jednej operacji do drugiej, ale wymaga to głębszego wyjaśnienia systemu typu Haskell.
Podsumowując
W terminologii Haskell monada to sparametryzowany typ, który jest instancją klasy typu Monad, która definiuje
>>=
wraz z kilkoma innymi operatorami. Mówiąc ogólnie, monada jest po prostu typem, dla którego>>=
zdefiniowano operację.Sam w sobie
>>=
jest po prostu nieporęcznym sposobem łączenia łańcuchów, ale przy obecności notacji do, która ukrywa „hydraulikę”, operacje monadyczne okazują się bardzo ładną i przydatną abstrakcją, przydatną w wielu miejscach w języku i użyteczną do tworzenia własnych mini-języków w języku.Dlaczego monady są trudne?
Dla wielu uczniów Haskell monady są przeszkodą, którą uderzają jak ceglany mur. Nie chodzi o to, że same monady są złożone, ale że implementacja opiera się na wielu innych zaawansowanych funkcjach Haskell, takich jak typy sparametryzowane, klasy typów i tak dalej. Problem polega na tym, że we / wy Haskell oparte są na monadach, a we / wy jest prawdopodobnie jedną z pierwszych rzeczy, które chcesz zrozumieć, ucząc się nowego języka - w końcu tworzenie programów, które nie produkują żadnych programów wynik. Nie mam bezpośredniego rozwiązania tego problemu z kurczakiem i jajkami, z wyjątkiem traktowania we / wy „magii dzieje się tutaj”, dopóki nie będziesz mieć wystarczającego doświadczenia z innymi częściami języka. Przepraszam.
Doskonały blog na monadach: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
źródło
Wyjaśnienie „czym jest monada” jest trochę jak powiedzenie „czym jest liczba?” Cały czas używamy liczb. Ale wyobraź sobie, że spotkałeś kogoś, kto nic nie wiedział o liczbach. Jak, do diabła , wyjaśniłbyś, jakie są liczby? Jak w ogóle zacząłbyś opisywać, dlaczego to może być przydatne?
Co to jest monada? Krótka odpowiedź: to specyficzny sposób łączenia operacji w łańcuch.
Zasadniczo piszesz kroki wykonywania i łączysz je razem z „funkcją wiązania”. (W Haskell ma nazwę
>>=
.) Możesz napisać wywołania do operatora powiązania lub użyć cukru składniowego, który zmusi kompilator do wstawienia tych wywołań funkcji. Ale tak czy inaczej, każdy krok jest oddzielony wywołaniem tej funkcji wiązania.Zatem funkcja wiązania jest jak średnik; oddziela kroki w procesie. Zadaniem funkcji wiązania jest pobranie danych wyjściowych z poprzedniego kroku i przekazanie ich do następnego kroku.
To nie brzmi zbyt mocno, prawda? Ale istnieje więcej niż jeden rodzaj monady. Dlaczego? W jaki sposób?
Cóż, funkcja bind może po prostu pobrać wynik z jednego kroku i podać go do następnego. Ale jeśli to „wszystko”, co robi monada… to nie jest zbyt przydatne. I to ważne, aby zrozumieć: każda przydatna monada oprócz bycia monadą robi coś jeszcze . Każda przydatna monada ma „specjalną moc”, co czyni ją wyjątkową.
(Monada, która nie robi nic specjalnego, nazywa się „monadą tożsamości”. Raczej jak funkcja tożsamości, brzmi to jak zupełnie bezcelowa rzecz, ale okazuje się, że nie jest… Ale to inna historia ™.)
Zasadniczo każda monada ma własną implementację funkcji wiązania. I możesz napisać funkcję powiązania, która będzie wykonywać kopie rzeczy między krokami wykonania. Na przykład:
Jeśli każdy krok zwraca wskaźnik powodzenia / niepowodzenia, można zlecić wykonanie następnego kroku tylko wtedy, gdy poprzedni się powiódł. W ten sposób nieudany krok przerywa całą sekwencję „automatycznie”, bez żadnego testowania warunkowego. ( Monada awarii ).
Rozszerzając ten pomysł, możesz wdrożyć „wyjątki”. ( Monada błędu lub monada wyjątku .) Ponieważ definiujesz je samodzielnie, a nie jako funkcję języka, możesz zdefiniować sposób ich działania. (Np. Być może chcesz zignorować dwa pierwsze wyjątki i przerwać tylko po zgłoszeniu trzeciego wyjątku).
Możesz sprawić, że każdy krok zwróci wiele wyników , i będziesz mieć nad nimi pętlę funkcji wiązania, podając każdy z nich do następnego kroku. W ten sposób nie musisz ciągle pisać pętli wszędzie, gdy masz do czynienia z wieloma wynikami. Funkcja wiązania „automatycznie” robi to wszystko za Ciebie. (The List Monad .)
Oprócz przekazywania „wyniku” z jednego kroku do drugiego, funkcja powiązania może również przekazywać dodatkowe dane . Te dane nie są teraz wyświetlane w kodzie źródłowym, ale nadal można uzyskać do nich dostęp z dowolnego miejsca, bez konieczności ręcznego przekazywania ich do każdej funkcji. (The Reader Monad .)
Możesz to zrobić, aby „dodatkowe dane” mogły zostać zastąpione. Umożliwia to symulowanie destrukcyjnych aktualizacji bez faktycznego wykonywania destrukcyjnych aktualizacji. ( State Monad i jego kuzyn Writer Monad .)
Ponieważ symulujesz tylko destrukcyjne aktualizacje, możesz w trywialny sposób robić rzeczy, które byłyby niemożliwe przy prawdziwych aktualizacjach destrukcyjnych. Na przykład możesz cofnąć ostatnią aktualizację lub przywrócić starszą wersję .
Możesz stworzyć monadę, w której obliczenia mogą zostać wstrzymane , aby można było wstrzymać program, wejść i majstrować przy wewnętrznych danych stanu, a następnie wznowić.
Możesz zaimplementować „kontynuacje” jako monadę. To pozwala złamać ludzkie umysły!
Wszystko to i więcej jest możliwe w przypadku monad. Oczywiście wszystko to jest również całkowicie możliwe bez monad. Korzystanie z monad jest po prostu znacznie łatwiejsze .
źródło
W rzeczywistości, w przeciwieństwie do powszechnego zrozumienia Monad, nie mają one nic wspólnego ze stanem. Monady są po prostu sposobem na zawijanie rzeczy i zapewniają metody wykonywania operacji na opakowanych rzeczach bez ich rozpakowywania.
Na przykład możesz utworzyć typ, aby zawinąć inny, w Haskell:
Aby zawinąć rzeczy, które definiujemy
Aby wykonać operacje bez rozpakowywania, powiedzmy, że masz funkcję
f :: a -> b
, możesz to zrobić, aby podnieść tę funkcję i działać na opakowanych wartościach:To wszystko, co trzeba zrozumieć. Okazuje się jednak, że istnieje bardziej ogólna funkcja wykonywania tego podnoszenia , a mianowicie
bind
:bind
może zrobić trochę więcejfmap
, ale nie odwrotnie. W rzeczywistościfmap
można go zdefiniować tylko w kategoriachbind
ireturn
. Definiując monadę, podajesz jej typ (tutaj byłWrapped a
), a następnie mówisz, jak działareturn
i jakbind
działa.Fajne jest to, że okazuje się, że jest to tak ogólny wzorzec, że wyskakuje w każdym miejscu, enkapsulacja stanu w czysty sposób jest tylko jednym z nich.
Dobry artykuł na temat tego, jak można wykorzystać monady do wprowadzenia zależności funkcjonalnych, a tym samym kontrolować kolejność oceny, tak jak w przypadku monady IO Haskella, zajrzyj do IO Inside .
Jeśli chodzi o rozumienie monad, nie przejmuj się tym zbytnio. Przeczytaj o nich to, co uważasz za interesujące i nie martw się, jeśli nie zrozumiesz od razu. W takim razie wystarczy nurkować w języku takim jak Haskell. Monady to jedna z tych rzeczy, w których praktyka przenika do twojego mózgu. Pewnego dnia nagle zdajesz sobie sprawę, że je rozumiesz.
źródło
Ale mógłbyś wymyślić Monady!
źródło
Monada to typ danych, który ma dwie operacje:
>>=
(akabind
) ireturn
(akaunit
).return
przyjmuje dowolną wartość i tworzy z nią instancję monady.>>=
pobiera instancję monady i odwzorowuje na niej funkcję. (Widać już, że monada jest dziwnym rodzajem typu danych, ponieważ w większości języków programowania nie można napisać funkcji, która przyjmuje dowolną wartość i tworzy z niej typ. Monady wykorzystują rodzaj polimorfizmu parametrycznego .)W notacji Haskell zapisany jest interfejs monady
Operacje te mają być zgodne z pewnymi „prawami”, ale nie jest to niezwykle ważne: „prawa” tylko kodyfikują sposób, w jaki powinny zachowywać się rozsądne implementacje operacji (w zasadzie to
>>=
ireturn
powinny zgadzać się co do tego, w jaki sposób wartości przekształcają się w instancje monad i to>>=
jest skojarzenie).Monady to nie tylko stan i operacje we / wy: wyodrębniają one wspólny wzorzec obliczeń obejmujący pracę ze stanem, operacje we / wy, wyjątki i niedeterminizm. Prawdopodobnie najprostszymi monadami do zrozumienia są listy i typy opcji:
gdzie
[]
i:
są konstruktorami listy,++
jest operatorem konkatenacjiJust
iNothing
sąMaybe
konstruktorami. Obie te monady zawierają typowe i użyteczne wzorce obliczeń dla swoich odpowiednich typów danych (zauważ, że żadne z nich nie ma nic wspólnego z efektami ubocznymi ani operacjami wejścia / wyjścia).Naprawdę musisz się bawić, pisząc jakiś nietrywialny kod Haskella, aby docenić, o czym są monady i dlaczego są one przydatne.
źródło
Najpierw powinieneś zrozumieć, czym jest funktor. Przedtem poznaj funkcje wyższego rzędu.
Funkcja wyższego rzędu jest po prostu funkcją, która przyjmuje funkcję jako argument.
Funktor jakikolwiek typ budowy
T
, dla których istnieje funkcję wyższego rzędu nazwaćmap
, który przekształca funkcją typua -> b
(biorąc pod uwagę dowolne dwa typya
ab
) do funkcjiT a -> T b
. Tamap
funkcja musi także być zgodna z prawami tożsamości i składu, tak aby następujące wyrażenia zwróciły prawdę dla wszystkichp
iq
(notacja Haskella):Na przykład konstruktor typu o nazwie
List
jest funktorem, jeśli jest wyposażony w funkcję typu,(a -> b) -> List a -> List b
która jest zgodna z powyższymi prawami. Jedyne praktyczne wdrożenie jest oczywiste. WynikowaList a -> List b
funkcja iteruje po podanej liście, wywołując(a -> b)
funkcję dla każdego elementu i zwraca listę wyników.Monada jest zasadniczo tylko funktor
T
z dwóch dodatkowych metodjoin
, typuT (T a) -> T a
iunit
(czasami nazywanereturn
,fork
lubpure
) typua -> T a
. W przypadku list w Haskell:Dlaczego to jest przydatne? Ponieważ możesz na przykład
map
nad listą z funkcją, która zwraca listę.Join
pobiera wynikową listę list i łączy je.List
jest monadą, ponieważ jest to możliwe.map
Następnie możesz napisać funkcję, która działajoin
. Ta funkcja jest nazywanabind
, lubflatMap
, lub(>>=)
, lub(=<<)
. Tak zwykle podaje się instancję monady w Haskell.Monada musi spełniać określone prawa, a mianowicie
join
muszą być asocjacyjne. Oznacza to, że jeśli mają wartośćx
typu[[[a]]]
następniejoin (join x)
powinna być równajoin (map join x)
. Ipure
musi być tożsamość zajoin
takie, żejoin (pure x) == x
.źródło
[Uwaga: Nadal próbuję w pełni wykorzystać monady. Oto, co do tej pory zrozumiałem. Jeśli to źle, mam nadzieję, że ktoś kompetentny zadzwoni do mnie na dywan.]
Arnar napisał:
Właśnie o to chodzi. Pomysł wygląda następująco:
Bierzesz jakąś wartość i zawijasz ją dodatkowymi informacjami. Podobnie jak wartość jest pewnego rodzaju (np. Liczba całkowita lub ciąg znaków), więc dodatkowe informacje są pewnego rodzaju.
Na przykład, ta dodatkowa informacja może być a
Maybe
lub anIO
.Następnie masz operatorów, którzy pozwalają ci operować na opakowanych danych, przenosząc te dodatkowe informacje. Operatorzy ci wykorzystują dodatkowe informacje, aby zdecydować, jak zmienić zachowanie operacji na opakowanej wartości.
Np. A
Maybe Int
może byćJust Int
lubNothing
. Teraz, jeśli dodasz aMaybe Int
doMaybe Int
, operator sprawdzi, czy oba są wJust Int
środku, a jeśli tak, rozpakujeInt
s, przekaże im operator dodawania, ponownie zapakuje wynikInt
w nowyJust Int
(który jest prawidłowyMaybe Int
), a zatem zwróci aMaybe Int
. Ale jeśli jeden z nich był wNothing
środku, operator natychmiast po prostu wróciNothing
, co znowu jest ważneMaybe Int
. W ten sposób możesz udawać, że twojeMaybe Int
są zwykłymi liczbami i wykonywać na nich regularną matematykę. Jeśli miałbyś dostać aNothing
, twoje równania nadal będą dawały właściwy wynik - bez konieczności zaśmiecania czeków naNothing
wszystko .Ale przykładem jest właśnie to, co się dzieje
Maybe
. Jeśli dodatkowa informacja byłaIO
, to ten operator specjalny został zdefiniowany dlaIO
s byłby wywoływany i mógłby zrobić coś zupełnie innego przed wykonaniem dodawania. (OK, dodanie dwóchIO Int
s razem jest prawdopodobnie nonsensowne - nie jestem jeszcze pewien.) (Ponadto, jeśli zwróciłeś uwagę naMaybe
przykład, zauważyłeś, że „zawijanie wartości dodatkowymi rzeczami” nie zawsze jest poprawne. Ale to trudne by być dokładnym, poprawnym i precyzyjnym bez bycia nieprzeniknionym).Zasadniczo „monada” z grubsza oznacza „wzór” . Ale zamiast książki pełnej nieformalnie wyjaśnionych i specjalnie nazwanych Wzorów, masz teraz konstrukcję językową - składnię i wszystko - która pozwala zadeklarować nowe wzorce jako rzeczy w twoim programie . (Niedokładność polega na tym, że wszystkie wzory muszą przybierać określoną formę, więc monada nie jest tak ogólna jak wzór. Myślę jednak, że jest to najbliższy termin, który większość ludzi zna i rozumie.)
I dlatego ludzie uważają monady za mylące: ponieważ są tak ogólną koncepcją. Pytanie o to, co czyni coś monadą, jest równie niejasne, co pytanie o to, co czyni coś wzorem.
Ale pomyśl o konsekwencjach posiadania wsparcia syntaktycznego w języku dla idei wzorca: zamiast czytać książkę Gang of Four i zapamiętywać budowę konkretnego wzorca, po prostu piszesz kod, który implementuje ten wzorzec w agnostyce, ogólny sposób raz, a potem gotowe! Następnie możesz ponownie użyć tego wzorca, takiego jak Odwiedzający, Strategia, Fasada lub cokolwiek innego, po prostu dekorując nim operacje w kodzie, bez konieczności jego ponownego wdrażania!
Dlatego ludzie, którzy rozumieją monady, uważają je za tak przydatne : to nie jest jakaś koncepcja wieży z kości słoniowej, z której snobowie intelektualni są dumni ze zrozumienia (OK, to też oczywiście teehee), ale w rzeczywistości upraszcza kod.
źródło
M (M a) -> M a
. To, że możesz zmienić to w jedno z typów,M a -> (a -> M b) -> M b
czyni je przydatnymi.Po wielu wysiłkach myślę, że w końcu rozumiem monadę. Po ponownym przeczytaniu mojej długiej krytyki przytłaczającej większości głosowanych odpowiedzi przedstawię to wyjaśnienie.
Aby zrozumieć monady, należy odpowiedzieć na trzy pytania:
Jak zauważyłem w moich oryginalnych komentarzach, zbyt wiele objaśnień monad zostało uwikłanych w pytanie nr 3, bez, a wcześniej naprawdę odpowiednio obejmujące pytanie 2 lub pytanie 1.
Dlaczego potrzebujesz monady?
Czyste języki funkcjonalne, takie jak Haskell, różnią się od języków imperatywnych, takich jak C lub Java, ponieważ czysty program funkcjonalny niekoniecznie jest wykonywany w określonej kolejności, krok po kroku. Program Haskell jest bardziej podobny do funkcji matematycznej, w której możesz rozwiązać „równanie” w dowolnej liczbie potencjalnych rzędów. Daje to szereg korzyści, między innymi eliminuje możliwość wystąpienia pewnych rodzajów błędów, szczególnie związanych z takimi rzeczami jak „stan”.
Istnieją jednak pewne problemy, które nie są tak łatwe do rozwiązania w przypadku tego stylu programowania. Niektóre rzeczy, takie jak programowanie konsoli i we / wy plików, wymagają, aby coś się wydarzyło w określonej kolejności lub muszą utrzymywać stan. Jednym ze sposobów rozwiązania tego problemu jest utworzenie rodzaju obiektu reprezentującego stan obliczeń oraz szereg funkcji, które przyjmują obiekt stanu jako dane wejściowe i zwracają nowy zmodyfikowany obiekt stanu.
Stwórzmy więc hipotetyczną wartość „stanu”, która reprezentuje stan ekranu konsoli. dokładne skonstruowanie tej wartości nie jest ważne, ale powiedzmy, że jest to tablica znaków ascii o długości bajtów, która reprezentuje to, co jest obecnie widoczne na ekranie, oraz tablica, która reprezentuje ostatni wiersz danych wejściowych wprowadzonych przez użytkownika, w pseudokodzie. Zdefiniowaliśmy niektóre funkcje, które przyjmują stan konsoli, modyfikują go i zwracają nowy stan konsoli.
Aby więc programować konsolę, ale w czysto funkcjonalny sposób, trzeba zagnieżdżać w sobie wiele wywołań funkcji.
Programowanie w ten sposób zachowuje „czysty” styl funkcjonalny, a jednocześnie wymusza zmiany w konsoli w określonej kolejności. Ale prawdopodobnie będziemy chcieli wykonać więcej niż kilka operacji jednocześnie, jak w powyższym przykładzie. Zagnieżdżanie funkcji w ten sposób zacznie się niezręcznie. To, czego chcemy, to kod, który robi zasadniczo to samo, co powyżej, ale jest napisany nieco bardziej w następujący sposób:
Byłby to rzeczywiście wygodniejszy sposób na napisanie tego. Jak to robimy?
Co to jest monada?
Gdy masz typ (taki jak
consolestate
), który definiujesz wraz z szeregiem funkcji zaprojektowanych specjalnie do działania na tym typie, możesz przekształcić cały pakiet tych rzeczy w „monadę”, definiując operator taki jak:
(bind), który automatycznie podaje wartości zwracane po lewej stronie, do parametrów funkcji po prawej stronie, alift
operator, który zmienia normalne funkcje, w funkcje, które działają z tym specyficznym rodzajem operatora powiązania.Jak wdrażana jest monada?
Zobacz inne odpowiedzi, które wydają się dość swobodne, aby przejść do szczegółów tego.
źródło
Po udzieleniu odpowiedzi na to pytanie kilka lat temu, uważam, że mogę poprawić i uprościć tę odpowiedź za pomocą ...
Monada to technika kompozycji funkcji, która uzewnętrznia leczenie niektórych scenariuszy wejściowych za pomocą funkcji komponowania
bind
, aby wstępnie przetworzyć dane wejściowe podczas kompozycji.W normalnym składzie funkcja
compose (>>)
służy do zastosowania kolejno funkcji złożonej do wyniku jej poprzednika. Co ważne, tworzona funkcja jest wymagana do obsługi wszystkich scenariuszy jej wprowadzania.(x -> y) >> (y -> z)
Ten projekt można ulepszyć poprzez restrukturyzację danych wejściowych, aby łatwiej było przesłuchać odpowiednie stany. Tak więc zamiast po prostu
y
wartość może stać sięMb
taka, jak na przykład,(is_OK, b)
jeśliy
zawiera pojęcie ważności.Na przykład, gdy wejście jest możliwe tylko liczbą, zamiast wrócić ciąg, który może zawierać obowiązkowo zawierać liczbę lub nie, można zrestrukturyzować wpisać do
bool
wskazywania obecności ważnego numeru oraz liczbę w krotce takie jak,bool * float
. Skomponowane funkcje nie będą już musiały analizować ciągu wejściowego, aby ustalić, czy istnieje liczba, ale mogłyby jedynie sprawdzićbool
część krotki.(Ma -> Mb) >> (Mb -> Mc)
Tutaj znowu kompozycja występuje naturalnie,
compose
więc każda funkcja musi obsługiwać wszystkie scenariusze danych wejściowych indywidualnie, chociaż teraz jest to o wiele łatwiejsze.Co jednak, jeśli moglibyśmy zlecić przesłuchanie w tych czasach, w których obsługa scenariusza jest rutyną. Na przykład, co jeśli nasz program nic nie robi, gdy dane wejściowe są nieprawidłowe, tak jak w przypadku, gdy
is_OK
jestfalse
. Gdyby tak było, wówczas złożone funkcje nie musiałyby same radzić sobie z tym scenariuszem, radykalnie upraszczając swój kod i powodując kolejny poziom ponownego użycia.Aby osiągnąć tę eksternalizację, moglibyśmy użyć funkcji
bind (>>=)
, aby wykonaćcomposition
zamiastcompose
. Jako taki, zamiast po prostu przenosić wartości z wyjścia jednej funkcji na wejście innejBind
, sprawdzałbyM
częśćMa
i zdecydował, czy i jak zastosować złożoną funkcję doa
. Oczywiście funkcjabind
zostałaby zdefiniowana specjalnie dla naszego konkretnegoM
, aby móc sprawdzić jej strukturę i wykonać dowolny rodzaj aplikacji, jaki chcemy. Niemniej jednak,a
może to być cokolwiek, ponieważbind
po prostu przekazujea
niezauważoną do złożonej funkcji, gdy określa ona konieczność zastosowania. Ponadto same złożone funkcje nie muszą już sobie z tym radzićM
część struktury wejściowej, upraszczając je. W związku z tym...(a -> Mb) >>= (b -> Mc)
lub bardziej zwięźleMb >>= (b -> Mc)
W skrócie, monada uzewnętrznia się, a tym samym zapewnia standardowe zachowanie wokół niektórych scenariuszy wejściowych, gdy dane wejściowe zostaną zaprojektowane w taki sposób, aby wystarczająco je ujawnić. Ten projekt jest
shell and content
modelem, w którym powłoka zawiera dane istotne dla zastosowania złożonej funkcji i jest odpytywana przez i pozostaje dostępna tylko dla tejbind
funkcji.Dlatego monada to trzy rzeczy:
M
shell dla gospodarstwa monada istotnych informacji,bind
funkcje realizowane w celu wykorzystania informacji powłoki w stosowaniu złożonych funkcji do wartości zawartości (S) nie znajdzie się w powłoce ia -> Mb
generujące wyniki zawierające monadyczne dane zarządzania.Ogólnie rzecz biorąc, dane wejściowe do funkcji są znacznie bardziej restrykcyjne niż dane wyjściowe, które mogą obejmować takie rzeczy, jak warunki błędu; stąd
Mb
struktura wynikowa jest na ogół bardzo przydatna. Na przykład operator dzielenia nie zwraca liczby, gdy dzielnik jest0
.Dodatkowo,
monad
s mogą obejmować funkcje zawijania, które zawijają wartości,a
w typ monadycznyMa
, i funkcje ogólnea -> b
, w funkcje monadycznea -> Mb
, poprzez zawijanie ich wyników po zastosowaniu. Oczywiściebind
, takie funkcje zawijania są specyficzne dlaM
. Przykład:Projekt
bind
funkcji zakłada niezmienne struktury danych i czyste funkcje, inne rzeczy stają się złożone i nie można zagwarantować. Jako takie istnieją prawa monadyczne:Dany...
Następnie...
Associativity
oznacza, żebind
zachowuje kolejność oceny bez względu nabind
to, kiedy zostanie zastosowana. Oznacza to, że w definicjiAssociativity
powyżej, siły wczesnej oceny, czy w nawiasachbinding
off
ag
spowoduje tylko w funkcji, która oczekujeMa
w celu zakończeniabind
. Stąd ocenaMa
musi zostać ustalona, zanim jej wartość będzie mogła zostać zastosowana,f
a wynik z kolei zastosowany dog
.źródło
Monada jest w rzeczywistości formą „operatora typu”. Zrobi trzy rzeczy. Najpierw „zawinie” (lub w inny sposób przekształci) wartość jednego typu w inny typ (zwykle nazywany „typem monadycznym”). Po drugie, wszystkie operacje (lub funkcje) będą dostępne dla typu bazowego dostępnego dla typu monadycznego. Wreszcie zapewni wsparcie w łączeniu siebie z inną monadą w celu uzyskania monady złożonej.
„Może monada” jest zasadniczo odpowiednikiem „typów zerowalnych” w Visual Basic / C #. Pobiera niedopuszczalny typ „T” i przekształca go w „Nullable <T>”, a następnie określa, co oznaczają wszystkie operatory binarne w Nullable <T>.
Efekty uboczne są przedstawione podobnie. Tworzona jest struktura, która zawiera opisy efektów ubocznych obok wartości zwracanej przez funkcję. Operacje „podniesione” następnie kopiują efekty uboczne, gdy wartości są przekazywane między funkcjami.
Nazywa się je raczej „monadami” niż łatwiejszą do uchwycenia nazwą „operatorów typów” z kilku powodów:
źródło
(Zobacz także odpowiedzi w Co to jest monada? )
Dobrą motywacją do Monads jest You Could Had Invented Monads sigfpe (Dan Piponi) ! (I może już masz) . Istnieje wiele innych samouczków dotyczących monad , z których wiele mylnie próbuje wyjaśnić monady w „prostych terminach” przy użyciu różnych analogii: jest to błędny samouczek monady ; Unikaj ich.
Jak mówi DR MacIver w Powiedz nam, dlaczego twój język jest do kitu :
Mówisz, że rozumiesz być może monadę? Dobrze, jesteś na dobrej drodze. Po prostu zacznij używać innych monad, a wcześniej czy później zrozumiesz, czym są monady.
[Jeśli jesteś zorientowany matematycznie, możesz zignorować dziesiątki samouczków i nauczyć się definicji lub postępować zgodnie z wykładami z teorii kategorii :) Główną częścią definicji jest to, że Monada M obejmuje „konstruktor typów”, który określa dla każdego istniejący typ „T” nowy typ „MT” oraz kilka sposobów przechodzenia między typami „zwykłymi” i typami „M”.]
Co zaskakujące, jednym z najlepszych wstępów do monad jest właściwie jedna z pierwszych prac naukowych przedstawiających monady, Monady Philipa Wadlera do programowania funkcjonalnego . W rzeczywistości ma praktyczne, nietrywialne motywujące przykłady, w przeciwieństwie do wielu sztucznych samouczków.
źródło
Monady mają kontrolować przepływ, jakie abstrakcyjne typy danych mają dla danych.
Innymi słowy, wielu programistów nie ma pojęcia o zestawach, listach, słownikach (lub skrótach lub mapach) i drzewach. W obrębie tych typów danych istnieje wiele specjalnych przypadków (na przykład InsertionOrderPreservingIdentityHashMap).
Jednak w konfrontacji z „przepływem” programu wielu programistów nie było narażonych na o wiele więcej konstrukcji niż wtedy, gdy przełącznik / case, robią, podczas gdy, goto (grr) i (być może) zamknięcia.
Zatem monada jest po prostu konstrukcją przepływu kontrolnego. Lepszym wyrażeniem zastępującym monadę byłby „typ kontroli”.
W związku z tym monada ma gniazda na logikę sterowania, instrukcje lub funkcje - odpowiednikiem w strukturach danych byłoby stwierdzenie, że niektóre struktury danych pozwalają dodawać i usuwać dane.
Na przykład monada „if”:
w najprostszym przypadku ma dwa miejsca - klauzulę i blok.
if
Monada jest zwykle wbudowany ocenić wynik klauzuli, a jeśli nie fałszywy, oceniać bloku. Wielu programistów nie jest zaznajomionych z monadami, kiedy uczą się „jeśli”, i po prostu nie trzeba rozumieć monad, aby pisać skuteczną logikę.Monady mogą stać się bardziej skomplikowane, podobnie jak struktury danych mogą stać się bardziej skomplikowane, ale istnieje wiele szerokich kategorii monad, które mogą mieć podobną semantykę, ale różne implementacje i składnię.
Oczywiście w ten sam sposób, w jaki struktury danych mogą być iterowane lub przemierzane, monady mogą być oceniane.
Kompilatory mogą, ale nie muszą, obsługiwać monad zdefiniowanych przez użytkownika. Haskell na pewno tak. Ioke ma pewne podobne możliwości, chociaż termin monada nie jest używany w tym języku.
źródło
Mój ulubiony samouczek Monady:
http://www.haskell.org/haskellwiki/All_About_Monads
(spośród 170 000 trafień w wyszukiwarce Google hasła „samouczek monady”!)
@Stu: Celem monad jest umożliwienie dodawania (zwykle) semantyki sekwencyjnej do czystego kodu; możesz nawet komponować monady (używając Monad Transformers) i uzyskać bardziej interesującą i skomplikowaną połączoną semantykę, na przykład parsowanie z obsługą błędów, stanem współdzielonym i logowaniem. Wszystko to jest możliwe w czystym kodzie, monady pozwalają po prostu wyodrębnić go i ponownie użyć w bibliotekach modułowych (zawsze dobre w programowaniu), a także zapewnić wygodną składnię, aby wyglądało to bezwzględnie.
Haskell ma już przeciążenie operatora [1]: używa klas typów w taki sam sposób, w jaki można używać interfejsów w Javie lub C #, ale Haskell akurat akceptuje również tokeny niealfanumeryczne takie jak + && i> jako identyfikatory infix. To przeciążenie operatora tylko na twój sposób patrzenia na niego, jeśli masz na myśli „przeciążenie średnika” [2]. Brzmi jak czarna magia i prosi o kłopoty z „przeciążeniem średnika” (przedsiębiorcy hakerzy Perla znają ten pomysł), ale chodzi o to, że bez monad nie ma średnika, ponieważ czysto funkcjonalny kod nie wymaga ani nie pozwala na wyraźne sekwencjonowanie.
Wszystko to brzmi o wiele bardziej skomplikowane niż trzeba. Artykuł sigfpe jest całkiem fajny, ale używa Haskella, aby to wyjaśnić, co w pewnym sensie nie łamie problemu z kurczakiem i jajami, rozumienia Haskella do grokowania Monad i zrozumienia Monad do grokowania Haskell.
[1] Jest to problem odrębny od monad, ale monady używają funkcji przeciążania operatora Haskell.
[2] Jest to również nadmierne uproszczenie, ponieważ operatorem łączenia działań monadycznych jest >> = (wymawiane „bind”), ale istnieje cukier składniowy („do”), który pozwala używać nawiasów klamrowych i średników i / lub wcięć i znaków nowej linii.
źródło
Ostatnio myślałem o Monadach w inny sposób. Myślałem o nich jako o matematycznym wyodrębnianiu kolejności egzekucji , co umożliwia nowe rodzaje polimorfizmu.
Jeśli używasz języka rozkazującego i piszesz niektóre wyrażenia w kolejności, kod ZAWSZE działa dokładnie w tej kolejności.
A w prostym przypadku, gdy używasz monady, wydaje się to samo - definiujesz listę wyrażeń, które występują w kolejności. Z wyjątkiem tego, że w zależności od używanej monady kod może być uruchamiany w kolejności (jak w monadzie IO), równolegle w kilku elementach jednocześnie (jak w monadzie z listy), może się zatrzymać w połowie (jak w monadzie może) , może wstrzymać się częściowo, aby wznowić później (jak w monadzie Wznowienia), może przewinąć do tyłu i zacząć od początku (jak w monadzie Transakcji), lub może przewinąć do tyłu, aby wypróbować inne opcje (jak w monadzie Logiki) .
A ponieważ monady są polimorficzne, możliwe jest uruchomienie tego samego kodu w różnych monadach, w zależności od potrzeb.
Ponadto w niektórych przypadkach można łączyć monady razem (z transformatorami monad), aby uzyskać wiele funkcji jednocześnie.
źródło
Nadal jestem nowy w monadach, ale pomyślałem, że podzielę się linkiem, który uznałem za naprawdę dobry do czytania (Z OBRAZAMI !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman / (bez przynależności)
Zasadniczo, ciepłą i rozmytą koncepcją, którą otrzymałem z tego artykułu, była koncepcja, że monady są w zasadzie adapterami, które pozwalają różnym funkcjom działać w sposób składalny, tj. Być w stanie zestawiać wiele funkcji i łączyć je ze sobą, nie martwiąc się o niespójny zwrot. rodzaje i takie. Tak więc funkcja BIND odpowiada za utrzymywanie jabłek z jabłkami i pomarańczy z pomarańczami, gdy próbujemy zrobić te adaptery. A funkcja LIFT odpowiada za przejmowanie funkcji „niższego poziomu” i „uaktualnianie” ich do pracy z funkcjami BIND i bycia również kompozycyjnymi.
Mam nadzieję, że dobrze to zrozumiałem, a co ważniejsze, mam nadzieję, że artykuł ma prawidłowy pogląd na monady. Jeśli nic więcej, ten artykuł pomógł zaostrzyć mój apetyt na więcej informacji na temat monad.
źródło
Oprócz doskonałych odpowiedzi powyżej, pozwól mi zaoferować link do następującego artykułu (autorstwa Patricka Thomsona), który wyjaśnia monady, odnosząc tę koncepcję do biblioteki jQuery biblioteki JavaScript (i jej sposobu użycia „łączenia łańcuchów metod” do manipulowania DOM) : jQuery to monada
Sama dokumentacja jQuery nie odnosi się do terminu „monada”, ale mówi o „wzorcu konstruktora”, który prawdopodobnie jest bardziej znany. Nie zmienia to faktu, że masz tam odpowiednią monadę, nawet nie zdając sobie z tego sprawy.
źródło
Monady nie są metaforami , ale praktycznie użyteczną abstrakcją wyłaniającą się ze wspólnego wzoru, jak wyjaśnia Daniel Spiewak.
źródło
Monada to sposób łączenia obliczeń, które mają wspólny kontekst. To jest jak budowanie sieci rur. Podczas budowy sieci przepływają przez nią dane. Ale kiedy skończyłem układać wszystkie bity razem z „bind” i „return”, wywołuję coś podobnego,
runMyMonad monad data
a dane przepływają przez potoki.źródło
W praktyce monada jest niestandardową implementacją operatora kompozycji funkcji, która dba o skutki uboczne i niekompatybilne wartości wejściowe i zwracane (do łączenia).
źródło
Jeśli dobrze zrozumiałem, IEnumerable pochodzi od monad. Zastanawiam się, czy to może być interesujący kąt podejścia dla tych z nas ze świata C #?
Oto, co warto, oto kilka linków do samouczków, które mi pomogły (i nie, wciąż nie rozumiem, co to są monady).
źródło
Dwie rzeczy, które pomogły mi najlepiej, gdy się o nich dowiedziałem:
Rozdział 8, „Parsery funkcyjne” z książki Grahama Huttona Programowanie w Haskell . Właściwie to wcale nie wspomina o monadach, ale jeśli możesz przejść przez rozdział i naprawdę zrozumieć wszystko w nim, a zwłaszcza to, jak ocenia się sekwencję operacji wiązania, zrozumiesz wewnętrzne elementy monad. Spodziewaj się, że zajmie to kilka prób.
Samouczek Wszystko o monadach . Daje to kilka dobrych przykładów ich użycia i muszę powiedzieć, że analogia w Appendexie działała dla mnie.
źródło
Monoid wydaje się być czymś, co zapewnia, że wszystkie operacje zdefiniowane na Monoid i obsługiwanym typie zawsze zwracają obsługiwany typ w Monoid. Np. Dowolna liczba + Dowolna liczba = Liczba, bez błędów.
Podczas gdy podział akceptuje dwa ułamki zwykłe i zwraca ułamek, który definiuje dzielenie przez zero jako Nieskończoność w haskell (który jest czasem ułamkiem) ...
W każdym razie wydaje się, że Monady są po prostu sposobem na zapewnienie, że łańcuch operacji zachowuje się w przewidywalny sposób, a funkcja, która twierdzi, że jest Num -> Num, złożona z innej funkcji Num-> Num wywoływanej za pomocą x powiedzmy, odpal pociski.
Z drugiej strony, jeśli mamy funkcję, która wystrzeliwuje pociski, możemy połączyć ją z innymi funkcjami, które również wystrzeliwują pociski, ponieważ nasza intencja jest jasna - chcemy wystrzelić pociski - ale nie spróbuje drukowanie „Hello World” z jakiegoś dziwnego powodu.
W Haskell main jest typu IO () lub IO [()], rozróżnienie jest dziwne i nie będę o tym dyskutować, ale oto, co myślę, że się dzieje:
Jeśli mam główny, chcę, aby wykonał łańcuch działań, powodem, dla którego uruchamiam program, jest uzyskanie efektu - zwykle przez IO. W ten sposób mogę łączyć operacje IO razem, aby - zrobić IO, nic więcej.
Jeśli spróbuję zrobić coś, co nie „zwróci IO”, program narzeka, że łańcuch nie płynie, lub w zasadzie „Jak to się ma do tego, co próbujemy zrobić - działanie IO”, wydaje się, że wymusza programiści, aby zachować ciąg myśli, nie zbaczając z drogi i nie myśląc o odpaleniu pocisków, jednocześnie tworząc algorytmy sortowania - które nie płyną.
Zasadniczo Monady wydają się wskazówką dla kompilatora, że „hej, znasz tę funkcję, która zwraca tutaj liczbę, tak naprawdę nie zawsze działa, czasami może wygenerować liczbę, a czasami nic, po prostu trzymaj ją umysł". Wiedząc o tym, jeśli spróbujesz zastosować akcję monadyczną, akcja monadyczna może działać jako wyjątek czasu kompilacji, mówiąc: „hej, to nie jest właściwie liczba, MOŻE to być liczba, ale nie możesz tego założyć, zrób coś w celu zapewnienia, że przepływ jest akceptowalny. ” co zapobiega nieprzewidzianemu zachowaniu programu - w dość dużym stopniu.
Wygląda na to, że w monadach nie chodzi o czystość ani kontrolę, ale o utrzymanie tożsamości kategorii, w której wszystkie zachowania są przewidywalne i zdefiniowane lub nie kompilują się. Nie możesz nic zrobić, gdy oczekuje się, że coś zrobisz, i nie możesz nic zrobić, jeśli oczekuje się, że nic nie zrobisz (widoczne).
Największym powodem, dla którego mogłem wymyślić Monady, jest - spójrz na kod Procedural / OOP, a zauważysz, że nie wiesz, gdzie program się kończy, a co kończy, wszystko, co widzisz, to dużo skoków i matematyki , magia i pociski. Nie będziesz w stanie go utrzymać, a jeśli możesz, poświęcisz sporo czasu na skupienie się na całym programie, zanim zrozumiesz jego część, ponieważ modułowość w tym kontekście opiera się na współzależnych „sekcjach” kodu, w którym kod jest zoptymalizowany tak, aby był jak najbardziej powiązany w celu zapewnienia wydajności / wzajemnych relacji. Monady są bardzo konkretne i dobrze zdefiniowane z definicji, i zapewniają, że przepływ programu jest możliwy do analizy i wyodrębnienia części trudnych do analizy - ponieważ one same są monadami. Monada wydaje się być „ lub zniszczyć wszechświat, a nawet zniekształcić czas - nie mamy pojęcia ani żadnych gwarancji, że TO TO, CO TO JEST. Monada GWARANTUJE, ŻE TO, CO TO JEST. co jest bardzo potężne. lub zniszczyć wszechświat, a nawet zniekształcić czas - nie mamy pojęcia ani żadnych gwarancji, że TO TO, CO TO JEST. Monada GWARANTUJE, ŻE TO, CO TO JEST. co jest bardzo potężne.
Wszystkie rzeczy w „prawdziwym świecie” wydają się być monadami, w tym sensie, że wiążą je określone obserwowalne prawa zapobiegające zamieszaniu. Nie oznacza to, że musimy naśladować wszystkie operacje tego obiektu, aby utworzyć klasy, zamiast tego możemy po prostu powiedzieć „kwadrat jest kwadratem”, nic oprócz kwadratu, nawet prostokąta ani koła, i „kwadrat ma powierzchnię o długości jednego z istniejących wymiarów pomnożonych przez siebie. Bez względu na kwadrat, który masz, jeśli jest to kwadrat w przestrzeni 2D, jego powierzchnia absolutnie nie może być niczym innym jak kwadrat podniesiony do kwadratu, jest to prawie banalne do udowodnienia. Jest to bardzo potężne, ponieważ nie musimy przedstawiać twierdzeń, aby upewnić się, że nasz świat jest taki, jaki jest, po prostu wykorzystujemy implikacje rzeczywistości, aby zapobiec upadkowi naszych programów.
Jestem prawie pewny, że się mylę, ale myślę, że to może komuś pomóc, więc mam nadzieję, że to komuś pomoże.
źródło
W kontekście Scali znajdziesz najprostszą definicję. Zasadniczo flatMap (lub bind) jest „asocjacyjny” i istnieje tożsamość.
Na przykład
UWAGA Ściśle mówiąc, definicja Monady w programowaniu funkcjonalnym nie jest tym samym, co definicja Monady w Teorii Kategorii , która jest zdefiniowana na przemian z
map
iflatten
. Chociaż są one swego rodzaju odpowiednikiem w niektórych mapowaniach. Prezentacje są bardzo dobre: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-categoryźródło
Ta odpowiedź zaczyna się od motywującego przykładu, przechodzi przez przykład, wyprowadza przykład monady i formalnie definiuje „monada”.
Rozważ te trzy funkcje w pseudokodzie:
f
przyjmuje uporządkowaną parę formularza<x, messages>
i zwraca uporządkowaną parę. Pozostawia nietknięty pierwszy element i dołącza"called f. "
do drugiego. To samo zg
.Możesz skomponować te funkcje i uzyskać oryginalną wartość wraz z łańcuchem, który pokazuje, w jakiej kolejności zostały wywołane funkcje:
Nie podoba ci się fakt, że
f
ig
oni są odpowiedzialni za dołączanie własnych wiadomości dziennika do poprzednich informacji logowania. (Wyobraźcie sobie tylko dla argumentu, że zamiast dodawać ciągif
ig
musi wykonywać skomplikowaną logikę na drugim elemencie pary. Powtórzenie tej skomplikowanej logiki w dwóch lub więcej różnych funkcjach byłoby utrudnieniem.)Wolisz pisać prostsze funkcje:
Ale spójrz na to, co się stanie, gdy je skomponujesz:
Problem polega na tym, że przekazanie pary do funkcji nie daje tego, czego chcesz. Ale co, jeśli możesz karmić parę do funkcji:
Czytać
feed(f, m)
jako „paszam
dof
”. Aby nakarmić parę<x, messages>
do funkcjif
jest przekazaćx
dof
, get<y, message>
out off
, a powrót<y, messages message>
.Zwróć uwagę, co się dzieje, gdy wykonujesz trzy czynności za pomocą swoich funkcji:
Po pierwsze: jeśli owinąć wartość i następnie paszy uzyskanej pary do funkcji:
To jest tak samo jak przemijanie wartości do funkcji.
Po drugie: jeśli wpiszesz parę w
wrap
:To nie zmienia pary.
Po trzecie: jeśli zdefiniujesz funkcję, która pobiera
x
i przekazuje informacjeg(x)
dof
:i nakarm parę:
To jest to samo, co karmienie pary
g
i karmienie otrzymanej paryf
.Masz większość monady. Teraz musisz tylko wiedzieć o typach danych w swoim programie.
Jakiego rodzaju jest wartość
<x, "called f. ">
? Zależy to od rodzaju wartościx
. Jeślix
jest typut
, to twoja para jest wartością typu „parat
i ciąg znaków”. Zadzwoń do tego typuM t
.M
jest konstruktorem typów:M
sam nie odnosi się do typu, aleM _
odnosi się do typu po wypełnieniu go pustym typem. AnM int
to para liczb całkowitych i ciąg znaków. NaM string
to para łańcucha i łańcucha. Itp.Gratulacje, stworzyłeś monadę!
Formalnie twoja monada jest krotką
<M, feed, wrap>
.Monada to krotka, w
<M, feed, wrap>
której:M
jest konstruktorem typów.feed
przyjmuje (funkcja, która przyjmujet
a zwraca anM u
) oraz anM t
i zwraca anM u
.wrap
bierzev
a zwraca anM v
.t
,u
iv
są dowolnymi trzema typami, które mogą, ale nie muszą być takie same. Monada spełnia trzy właściwości udowodnione dla konkretnej monady:Podawanie owiniętego
t
do funkcji jest równoznaczne z przekazaniem nieopakowanegot
do funkcji.Formalnie:
feed(f, wrap(x)) = f(x)
Karmienie
M t
INTOwrap
nie robi nic doM t
.Formalnie:
feed(wrap, m) = m
Podaj
M t
(wywołajm
) do funkcji, którat
wg
M u
(zadzwońn
) zg
n
sięf
jest taki sam jak
m
dog
n
zg
n
dof
Formalnie:
feed(h, m) = feed(f, feed(g, m))
gdzieh(x) := feed(f, g(x))
Zazwyczaj
feed
nazywa siębind
(AKA>>=
in Haskell) iwrap
nazywa sięreturn
.źródło
Spróbuję wyjaśnić
Monad
w kontekście Haskella.W programowaniu funkcjonalnym ważny jest skład funkcji. Pozwala naszemu programowi składać się z małych, łatwych do odczytania funkcji.
Powiedzmy, że mamy dwie funkcje:
g :: Int -> String
if :: String -> Bool
.Możemy to zrobić
(f . g) x
, tak samo jakf (g x)
, gdziex
jestInt
wartość.Podczas tworzenia kompozycji / stosowania wyniku jednej funkcji do drugiej ważne jest dopasowanie typów. W powyższym przypadku typ zwróconego wyniku
g
musi być taki sam, jak typ zaakceptowany przezf
.Ale czasami wartości są kontekstowe, co sprawia, że nieco łatwiej jest ustawiać typy w szeregu. (Posiadanie wartości w kontekstach jest bardzo przydatne. Na przykład
Maybe Int
typ reprezentujeInt
wartość, która może nie istnieć,IO String
typ reprezentujeString
wartość występującą w wyniku wywołania pewnych efektów ubocznych.)Powiedzmy, że teraz mamy
g1 :: Int -> Maybe String
if1 :: String -> Maybe Bool
.g1
if1
są bardzo podobne dog
if
odpowiednio.Nie możemy zrobić
(f1 . g1) x
lubf1 (g1 x)
, gdziex
jestInt
wartość. Zwrócony typ wynikug1
nie jest tym, cof1
oczekuje.Mogliśmy komponować
f
ig
z.
operatorem, ale teraz nie możemy komponowaćf1
ig1
z.
. Problem polega na tym, że nie możemy bezpośrednio przekazać wartości w kontekście do funkcji, która oczekuje wartości, która nie jest w kontekście.Czy nie byłoby miło, gdybyśmy wprowadzili operatora do komponowania
g1
if1
takiego, który możemy pisać(f1 OPERATOR g1) x
?g1
zwraca wartość w kontekście. Wartość zostanie wyjęta z kontekstu i zastosowana dof1
. I tak, mamy takiego operatora. Jest<=<
.Mamy również
>>=
operatora, który robi dla nas dokładnie to samo, choć w nieco innej składni.Piszemy:
g1 x >>= f1
.g1 x
jestMaybe Int
wartością.>>=
Operator pomaga przyjąć, żeInt
wartość z „chyba-nie-tam” kontekście, i zastosować go dof1
. Wynikf1
, który jestMaybe Bool
, będzie wynikiem całej>>=
operacji.I na koniec, dlaczego jest
Monad
przydatny? PonieważMonad
jest to klasa typu, która definiuje>>=
operator, bardzo podobnie jakEq
klasa typu, która definiuje operatory==
i/=
.Podsumowując,
Monad
klasa typu definiuje>>=
operator, który pozwala nam przekazywać wartości w kontekście (nazywamy te wartości monadyczne) do funkcji, które nie oczekują wartości w kontekście. Zadbamy o kontekst.Jeśli jest tutaj jedna rzecz do zapamiętania, to
Monad
pozwala ona na składanie funkcji, które obejmuje wartości w kontekstach .źródło
tl; dr
Prolog
Operator aplikacji
$
funkcjijest zdefiniowany kanonicznie
pod względem zastosowania funkcji prymitywnej Haskell
f x
(infixl 10
).Skład
.
określa się$
jakoi spełnia równoważności
forall f g h.
.
ma asocjację iid
jest jego prawą i lewą tożsamością.Potrójne kleisli
W programowaniu monada jest konstruktorem typu funktora z instancją klasy typu monada. Istnieje kilka równoważnych wariantów definicji i implementacji, z których każda zawiera nieco inne intuicje na temat abstrakcji monady.
Funktor to rodzaj konstruktora
f
typów* -> *
z instancją klasy typu funktora.Oprócz przestrzegania statycznie wymuszonego protokołu typu, instancje klasy typu funktora muszą być zgodne z algebraicznymi prawami funktora
forall f g.
Obliczenia Functor mają typ
Obliczenia
c r
obejmują wynikir
w kontekściec
.Unary monadyczne funkcje lub strzały Kleisli mają ten typ
Strzałki Kleisi są funkcjami, które pobierają jeden argument
a
i zwracają monadyczne obliczeniam b
.Monady są kanonicznie zdefiniowane w kategoriach potrójnej Kleisli
forall m. Functor m =>
zaimplementowany jako klasa typu
Tożsamość Kleisli
return
jest Kleisli strzałka, która promuje wartościt
do monadycznej kontekściem
. Aplikacja Extension lub Kleisli=<<
stosuje strzałkę Kleislia -> m b
do wyników obliczeńm a
.Skład Kleisli
<=<
definiuje się w kategoriach rozszerzenia jako<=<
tworzy dwie strzałki Kleisli, stosując lewą strzałkę do wyników zastosowania prawej strzałki.Instancje klasy typu monada muszą być zgodne z prawami monady , najbardziej elegancko określonymi pod względem składu Kleisli:
forall f g h.
<=<
ma asocjację ireturn
jest jego prawą i lewą tożsamością.Tożsamość
Typ tożsamości
jest funkcją tożsamości dla typów
Interpretowany jako funktor,
W kanonicznym Haskell monada tożsamości jest zdefiniowana
Opcja
Typ opcji
koduje obliczenia,
Maybe t
które niekoniecznie dają wynikt
, obliczenia, które mogą „zawieść”. Opcja monada jest zdefiniowanaa -> Maybe b
jest stosowany do wyniku tylko wtedy, gdyMaybe a
daje wynik.Liczby naturalne mogą być kodowane jako liczby całkowite większe lub równe zero.
Liczby naturalne nie są zamykane przy odejmowaniu.
Opcja monada obejmuje podstawową formę obsługi wyjątków.
Lista
Monada listy nad typem listy
i jego addytywna operacja monoidu „append”
koduje obliczenia nieliniowe,
[t]
dając naturalną liczbę0, 1, ...
wynikówt
.Rozszerzenie
=<<
łączy++
wszystkie listy[b]
wynikające z zastosowaniaf x
strzałki Kleislia -> [b]
do elementów[a]
pojedynczej listy wyników[b]
.Niech właściwe dzielniki dodatniej liczby całkowitej
n
będąnastępnie
Podczas definiowania klasy typu monada zamiast rozszerzenia
=<<
standard Haskell używa operatora odwracania, czyli bind>>=
.Dla uproszczenia wyjaśnienie to wykorzystuje hierarchię klas typów
W Haskell obecna standardowa hierarchia to
ponieważ nie tylko każda monada jest funktorem, ale każda aplikacja jest funktorem, a każda monada jest również aplikacją.
Używając monady listy, pseudokodu imperatywnego
z grubsza przekłada się na blok do ,
równoważne rozumienie monady ,
i wyrażenie
Czy notacje i rozumienia monad są cukrem syntaktycznym dla wyrażeń wiązania zagnieżdżonego. Operator wiązania służy do wiązania nazw lokalnych wyników monadycznych.
gdzie
Funkcja wartownika jest zdefiniowana
gdzie typ jednostki lub „pusta krotka”
Addytywne monady, które obsługują wybór i niepowodzenie, można wyodrębnić za pomocą klasy typu
gdzie
fail
i<|>
tworzą monoidforall k l m.
i
fail
jest absorbującym / anihilującym zerowym elementem monad addytywnychJeśli w
even p
jest prawdą, wówczas wartownik tworzy[()]
i, z definicji>>
, lokalną funkcję stałąjest stosowany do wyniku
()
. Jeśli false, to wartownik produkuje listę monad'sfail
([]
), co nie daje żadnego rezultatu dla zastosowania strzałki Kleisli>>
, więc top
jest to pomijane.Stan
Niesławnie monady są używane do kodowania obliczeń stanowych.
Procesor stan jest funkcją
który zmienia stan
st
i daje wynikt
. stanst
może być cokolwiek. Nic, flaga, liczba, tablica, uchwyt, maszyna, świat.Typ procesorów stanu jest zwykle nazywany
Monada procesora stanu jest uprzywilejowanym
* -> *
funktoremState st
. Strzałki Kleisli monady procesora stanu są funkcjamiW kanonicznym Haskell jest zdefiniowana leniwa wersja monady procesora stanu
Procesor stanu jest uruchamiany przez podanie stanu początkowego:
Dostęp do stanu zapewniają prymitywy
get
iput
metody abstrakcji nad stanowymi monadami:m -> st
deklaruje funkcjonalną zależność typu stanust
od monadym
; że aState t
, na przykład, określi typ stanu jakot
unikalny.z typem jednostki stosowanym analogicznie jak
void
w C.gets
jest często używany z akcesoriami pola rekordu.Stan monadowy odpowiednik zmiennej wątków
gdzie
s0 :: Int
jest równie referencyjnie przejrzysty, ale nieskończenie bardziej elegancki i praktycznymodify (+ 1)
jest obliczeniem typuState Int ()
, z wyjątkiem jego efektu równoważnego zreturn ()
.Monadowe prawo asocjacyjne można zapisać w kategoriach
>>=
forall m f g.
lub
Podobnie jak w programowaniu zorientowanym na wyrażenia (np. Rust), ostatnia instrukcja bloku reprezentuje jego wydajność. Operator wiązania jest czasem nazywany „programowalnym średnikiem”.
Operacje podstawowe struktury kontroli iteracji ze strukturalnego programowania imperatywnego są emulowane monadycznie
Wejście wyjście
Monada procesora światowego we / wy jest połączeniem czystego Haskella i świata rzeczywistego, funkcjonalnej denotatywnej i bezwzględnej semantyki operacyjnej. Bliski odpowiednik faktycznego ścisłego wdrożenia:
Interakcje ułatwiają nieczyste prymitywy
Zanieczyszczenie kodu używającego
IO
prymitywów jest na stałe protokołowane przez system typów. Ponieważ czystość jest niesamowita, to, co się dziejeIO
, pozostajeIO
.A przynajmniej powinien.
Podpis typu programu Haskell
rozwija się do
Funkcja, która przekształca świat.
Epilog
Kategoria, w której obiektami są typy Haskella, a morfizmy, w których morfizmy są funkcjami między typami Haskella, jest „szybka i luźna”, kategoria
Hask
.Funktor
T
to odwzorowanie z kategoriiC
na kategorięD
; dla każdego obiektu wC
obiekcie wD
i dla każdego morfizmu w
C
morfizmie wD
gdzie
X
,Y
są obiektyC
.HomC(X, Y)
jest klasą homomorfizmu wszystkich morfizmówX -> Y
wC
. Funktor musi zachować tożsamość i kompozycję morfizmu, „strukturę”C
, wD
.Kategoria Kleisli z kategorii
C
jest podana przez Kleisli Tripleendofunkcji
(
f
), morfizm tożsamościeta
(return
) i operator rozszerzenia*
(=<<
).Każdy morfizm Kleisli w
Hask
przez operatora wewnętrznego
otrzymuje morfizm w
Hask
kategorii KleisliSkład w kategorii Kleisli
.T
jest podany w odniesieniu do rozszerzeniai spełnia aksjomaty kategorii
który, stosując transformacje równoważności
pod względem przedłużenia są podane kanonicznie
Monady można również zdefiniować w kategoriach nie rozszerzenia Kleisliana, ale naturalną transformację
mu
w programowaniu zwanymjoin
. Monada jest definiowanamu
jako potrójny w stosunku do kategoriiC
endofunkcjii dwie naturalne transformacje
spełnianie równoważności
Klasa typu monada jest następnie definiowana
Kanoniczna
mu
implementacja opcji monada:concat
funkcjajest
join
monadą z listy.Implementacje
join
można tłumaczyć z formularza rozszerzenia przy użyciu równoważnościOdwrotne tłumaczenie z
mu
na formularz rozszerzenia podano przezPhilip Wadler: Monady do programowania funkcjonalnego
Simon L Peyton Jones, Philip Wadler: Imperatywne programowanie funkcjonalne
Jonathan MD Hill, Keith Clarke: Wprowadzenie do teorii kategorii, monady teorii kategorii i ich związek z programowaniem funkcjonalnym ´
Kategoria Kleisli
Eugenio Moggi: Pojęcia obliczeń i monady
Czym nie jest monada
od generalizacji monad po strzały Johna Hughesa
źródło
Świat potrzebuje kolejnego wpisu na blogu o monadach, ale myślę, że jest to przydatne do identyfikacji istniejących monad na wolności.
źródło
http://code.google.com/p/monad-tutorial/ jest w toku, aby rozwiązać dokładnie to pytanie.
źródło
Niech poniższe „
{| a |m}
” reprezentują jakiś fragment danych monadycznych. Typ danych, który reklamujea
:Funkcja
f
wie, jak stworzyć monadę, gdyby tylko miałaa
:Tutaj widzimy funkcję,
f
próbuje ocenić monadę, ale zostaje zganiona.Funtion,
f
znajduje sposób na wyodrębnieniea
za pomocą>>=
.Mało
f
wie, monada i>>=
są w zmowie.Ale o czym tak naprawdę mówią? To zależy od monady. Rozmawianie wyłącznie w sposób abstrakcyjny ma ograniczone zastosowanie; musicie mieć trochę doświadczenia z poszczególnymi monadami, aby rozwinąć zrozumienie.
Na przykład typ danych Może
ma instancję monady, która będzie działać następująco ...
W którym przypadku jest
Just a
Ale w przypadku
Nothing
Więc monada Może pozwala na kontynuowanie obliczeń, jeśli faktycznie zawiera to,
a
co reklamuje, ale przerywa obliczenia, jeśli tego nie robi. Rezultatem jest jednak nadal kawałek danych monadycznych, choć nie wynik wyjściowyf
. Z tego powodu monada Może reprezentuje kontekst niepowodzenia.Różne monady zachowują się inaczej. Listy to inne typy danych z instancjami monadycznymi. Zachowują się następująco:
W tym przypadku funkcja wiedziała, jak utworzyć listę na podstawie danych wejściowych, ale nie wiedziała, co zrobić z dodatkowymi danymi wejściowymi i dodatkowymi listami. Wiązanie
>>=
pomogłof
, łącząc wiele wyników. Podaję ten przykład, aby pokazać, że chociaż>>=
jest on odpowiedzialny za rozpakowywaniea
, ma również dostęp do ostatecznego związanego wynikuf
. Rzeczywiście, nigdy go nie wyodrębni,a
chyba że będzie wiedział, że ostateczny wynik ma ten sam typ kontekstu.Istnieją inne monady, które służą do reprezentowania różnych kontekstów. Oto kilka charakterystycznych cech kilku innych.
IO
Monada rzeczywistości nie mieća
, ale ona wie faceta i będzie toa
dla ciebie.State st
Monada ma tajny zapasst
, że to minie, abyf
pod stołem, choćf
po prostu przyszedł z prośbą oa
.Reader r
Monada jest podobna doState st
, choć tylko pozwalaf
przyjrzećr
.Chodzi o to, że każdy typ danych, które są zadeklarowane jako Monada, deklaruje pewien kontekst wokół wydobywania wartości z monady. Duży zysk z tego wszystkiego? Cóż, łatwo jest obliczyć obliczenia w pewnym kontekście. Może być jednak bałagan podczas łączenia wielu obliczeń obciążonych kontekstem. Operacje monady zajmują się rozwiązywaniem interakcji kontekstu, aby programista nie musiał.
Zauważ, że użycie tego rozwiązania
>>=
ułatwia bałagan, odbierając trochę autonomiif
. Oznacza to, że w powyższym przypadkuNothing
na przykładf
nie można już zdecydować, co zrobić w przypadkuNothing
; jest zakodowane w>>=
. To jest kompromis. Gdyby było konieczne,f
aby zdecydować, co zrobić w przypadkuNothing
, tof
powinna być funkcja odMaybe a
doMaybe b
. W tym przypadku,Maybe
bycie monadą nie ma znaczenia.Zauważ jednak, że czasami typ danych nie eksportuje swoich konstruktorów (patrząc na ciebie IO), a jeśli chcemy pracować z reklamowaną wartością, nie mamy innego wyboru, jak pracować z jej interfejsem monadycznym.
źródło
Monada to rzecz używana do enkapsulacji obiektów o zmiennym stanie. Najczęściej spotyka się go w językach, które inaczej nie pozwalają na modyfikowalny stan (np. Haskell).
Przykładem może być we / wy pliku.
Mógłbyś użyć monady dla pliku I / O, aby wyizolować zmienną naturę stanu tylko kodowi, który użył Monady. Kod wewnątrz Monady może skutecznie ignorować zmieniający się stan świata poza Monadą - znacznie ułatwia to rozumowanie ogólnego efektu twojego programu.
źródło