Przeczytałem wiele postów, które wyjaśniają, czym są monady, jak unit
i jak bind
działają, niektóre z nich pogrążają się w teorii kategorii tak abstrakcyjnej (przynajmniej dla mnie), że powodują krwawienie oczu, niektóre ignorują to całkowicie i dotykają dziwnych analogii burrito, pudełka i co nie.
Po kilku tygodniach badań i wielu smażonych neuronach (chyba) rozumiem, jak działają Monady. Ale wciąż jest jedna rzecz, która nie pozwala mi zrozumieć, coś, o czym tak naprawdę niewiele postów dotyczy (poza IO i stanem):
DLACZEGO?
Dlaczego monady mają znaczenie? Dlaczego są tak ważne? Jakie problemy rozwiązują? Czy problemy te można rozwiązać tylko za pomocą Monad, czy też istnieją inne sposoby?
functional-programming
problem-solving
monad
Dummy Me
źródło
źródło
Odpowiedzi:
Nie potrzebujesz monad, aby cokolwiek rozwiązać. Po prostu upraszczają niektóre rzeczy. Wiele osób jest zbyt abstrakcyjnych i teoretycznych, tłumacząc monady. Przeważnie monady są wzorcem, który pojawia się wielokrotnie w programowaniu. Rozpoznając ten wzorzec, możemy uprościć nasz kod i uniknąć ponownego wprowadzania niektórych funkcji.
Dla Haskell, z konkretnego punktu widzenia, najbardziej widoczną rzeczą, którą monady umożliwiają, jest notacja . Rozumienie list w języku Haskell i innych językach również w dużym stopniu wykorzystuje monady. Możesz także tworzyć biblioteki takie jak Control.Monad .
Wszystkie zapewniają przydatne uproszczenia, a po zaimplementowaniu go dla jednej monady automatycznie otrzymujesz go dla wszystkich monad. Jest to jeden z głównych powodów, dla których ponowne użycie kodu jest o wiele łatwiejsze do programowania funkcjonalnego niż w przypadku innych paradygmatów.
źródło
IO
jest najbardziej znaną monadą Haskella , ale nie wymieniłem przykładów pojedynczych monad. Wymieniłem przykłady nadrzędnych abstrakcji, które umożliwiają. Próbowałem dowiedzieć się, dlaczego na przykład pierwszy facet, który pomyśli o użyciu monady dla IO, uznałby, że ogólnie jest to dobry pomysł.Łatwiej jest zrozumieć, jeśli spojrzysz na poszczególne monady i zobaczysz, jakie problemy rozwiązują. Na przykład w Haskell:
IO: Pozwala na reprezentację IO w systemie typów, dzięki czemu można usunąć oddzielne czyste funkcje od funkcji wykonujących IO.
Lista: Umożliwia wykonywanie porównań list i obliczeń nodeterministycznych.
Może: Lepsza alternatywa dla zer i obsługa czegoś porównywalnego z operatorem zerowania w języku C #.
Parsec: Wygodny DSL do pisania parserów.
Łatwo więc (mam nadzieję) zobaczyć uzasadnienie poszczególnych monad, ponieważ wszystkie są całkiem przydatne. Ale problemy, które rozwiązują, są również zupełnie inne i na pierwszy rzut oka nie mają ze sobą wiele wspólnego, z wyjątkiem tego, że wszystkie są związane z pewną logiką operacji łańcuchowych. Monady są przydatne, ponieważ pozwalają budować tak różnorodne narzędzia.
Czy powyższe przykłady można zaimplementować bez monad? Z pewnością można je zaimplementować w sposób ad-hoc, ale posiadanie monad wbudowanych w język pozwala na bezpośrednie wsparcie języka, takie jak
do
adnotacja, która działa ze wszystkimi monadami.źródło
Jedną z rzeczy, która sprawia, że jest to mylące, jest to, że „popularne” funkcje jak
bind
i<*>
są zorientowane na praktykę. Ale aby zrozumieć pojęcia, łatwiej jest najpierw spojrzeć na inne funkcje. Warto również zauważyć, że monady wyróżniają się, ponieważ są nieco przesadzone w porównaniu do innych powiązanych koncepcji. Zacznę więc od funktorów.Functors oferują funkcję (w notacji Haskell)
fmap :: (Functor f) => (a -> b) -> f a -> f b
. Innymi słowy, masz kontekstf
, w którym możesz podnieść funkcję. Jak możesz sobie wyobrazić, prawie wszystko jest funktorem. Listy, być może, albo funkcje, I / O, krotki, parsery ... Każdy przedstawia kontekst, w którym może pojawić się wartość. Możesz więc pisać niezwykle wszechstronne funkcje, które działają w prawie każdym kontekście, używającfmap
lub jego wbudowanego wariantu<$>
.Jakie inne rzeczy chcesz robić z kontekstami? Możesz połączyć dwa konteksty. Więc może chcesz uzyskać uogólnienie
zip :: [a] -> [b] -> [(a,b)]
na przykład tak:pair :: (Monoidal f) => f a -> f b -> f (a,b)
.Ale ponieważ jest to jeszcze bardziej przydatne w praktyce, biblioteki Haskell oferują zamiast tego
Applicative
, co jest kombinacjąFunctor
iMonoidal
, A takżeUnit
, co po prostu dodaje, że możesz w rzeczywistości umieścić wartości „wewnątrz” swojego kontekstuunit
.Możesz pisać niezwykle ogólne funkcje, po prostu podając te trzy rzeczy na temat kontekstu, w którym pracujesz.
Monad
to po prostu kolejna rzecz, którą można dodać do tego. To, czego wcześniej nie wspomniałem, to to, że masz już dwa sposoby łączenia dwóch kontekstów: możesz nie tylkopair
je, ale możesz także układać w stosy, np. Możesz mieć listę list. W kontekście We / Wy przykładem może być akcja We / Wy, która może odczytać inne akcje We / Wy z pliku, więc masz typFilePath -> IO (IO a)
. Jak możemy pozbyć się tego stosu, aby uzyskać funkcję wykonywalnąIO a
? Tam właśnie pojawia sięMonad
sjoin
, pozwala nam połączyć dwa konteksty tego samego typu. To samo dotyczy parserów, może itd. Ibind
jest po prostu bardziej praktycznym sposobem użyciajoin
Tak więc kontekst monadyczny musi oferować tylko cztery rzeczy i może być używany z prawie wszystkimi maszynami opracowanymi dla I / O, parserów, awarii itp.
źródło
Monady pozwalają wyrażać różne nieczyste obliczenia, a także upraszczają kod
I, co ważne, bez kompromisów z konstrukcjami czystego języka i uzyskiwania z niego czystszego języka
źródło
Maybe
pojęcia lub nie są powiązane z niczym zewnętrznym.