Odpowiedzi:
Użycie Maybe
(lub jego kuzyna, Either
który działa zasadniczo w ten sam sposób, ale pozwala zwrócić dowolną wartość zamiast Nothing
) służy nieco innym celowi niż wyjątki. W kategoriach Java przypomina to sprawdzony wyjątek, a nie wyjątek środowiska wykonawczego. Reprezentuje ona coś oczekiwanego które masz do czynienia, zamiast błędu nie spodziewał.
Tak więc funkcja indexOf
taka zwróciłaby Maybe
wartość, ponieważ oczekujesz możliwości, że elementu nie ma na liście. Przypomina to powrót null
z funkcji, z wyjątkiem bezpiecznego typu, który zmusza cię do załatwienia null
sprawy. Either
działa w ten sam sposób, z tą różnicą, że możesz zwrócić informacje związane z przypadkiem błędu, więc jest to bardziej podobne do wyjątku niż Maybe
.
Jakie są zalety podejścia Maybe
/ Either
? Po pierwsze, jest to obywatel pierwszej klasy tego języka. Porównajmy funkcję używającą Either
do zgłaszania wyjątku. W przypadku wyjątku jedynym realnym wyjściem jest try...catch
stwierdzenie. W przypadku tej Either
funkcji można użyć istniejących kombinatorów, aby uczynić kontrolę przepływu bardziej przejrzystą. Oto kilka przykładów:
Po pierwsze, powiedzmy, że chcesz wypróbować kilka funkcji, które mogą powodować błędy w szeregu, dopóki nie otrzymasz takiej, która tego nie robi. Jeśli nie otrzymasz żadnego bez błędów, chcesz zwrócić specjalny komunikat o błędzie. Jest to w rzeczywistości bardzo przydatny wzór, ale korzystanie z niego byłoby okropnym bólem try...catch
. Na szczęście, ponieważ Either
jest to po prostu wartość normalna, możesz użyć istniejących funkcji, aby uczynić kod znacznie wyraźniejszym:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Innym przykładem jest posiadanie opcjonalnej funkcji. Załóżmy, że masz kilka funkcji do uruchomienia, w tym jedną, która próbuje zoptymalizować zapytanie. Jeśli to się nie powiedzie, chcesz, aby wszystko inne działało tak czy inaczej. Możesz napisać kod coś takiego:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Oba te przypadki są wyraźniejsze i krótsze niż użycie try..catch
, a co ważniejsze, bardziej semantyczne. Używanie funkcji podobnej do <|>
lub optional
sprawia, że twoje intencje są znacznie bardziej przejrzyste niż używanie try...catch
zawsze do obsługi wyjątków.
Pamiętaj również, że nie musisz zaśmiecać kodu liniami takimi jak if a == Nothing then Nothing else ...
! Istotą leczenia Maybe
i Either
jak monady jest, aby tego uniknąć. Możesz zakodować semantykę propagacji w funkcji wiązania, aby uzyskać bezpłatne sprawdzanie wartości null / error. Jedyne, co musisz jawnie sprawdzić, to czy chcesz zwrócić coś innego niż Nothing
podane Nothing
, a nawet wtedy jest to łatwe: istnieje kilka standardowych funkcji bibliotecznych, które poprawią ten kod.
Wreszcie kolejną zaletą jest to, że typ Maybe
/ Either
jest po prostu prostszy. Nie ma potrzeby rozszerzania języka o dodatkowe słowa kluczowe lub struktury kontrolne - wszystko to tylko biblioteka. Ponieważ są to po prostu wartości normalne, upraszcza to system typów - w Javie musisz rozróżniać typy (np. Typ zwracany) i efekty (np. throws
Instrukcje), których nie używasz Maybe
. Zachowują się również tak samo, jak każdy inny typ zdefiniowany przez użytkownika - nie ma potrzeby wprowadzania specjalnego kodu obsługi błędów do języka.
Kolejną wygraną jest to, że Maybe
/ Either
są funktorami i monadami, co oznacza, że mogą skorzystać z istniejących funkcji kontrolujących monadę (których jest sporo) i ogólnie grać ładnie z innymi monadami.
To powiedziawszy, są pewne zastrzeżenia. Po pierwsze, ani Maybe
nie Either
zastępuj niezaznaczonych wyjątków. Będziesz potrzebować innego sposobu radzenia sobie z takimi rzeczami, jak dzielenie przez 0, po prostu dlatego, że każdy pojedynczy podział zwróci Maybe
wartość.
Innym problemem jest zwracanie wielu rodzajów błędów (dotyczy to tylko Either
). Z wyjątkami możesz rzucać różne typy wyjątków w tej samej funkcji. z Either
, dostajesz tylko jeden typ. Można to obejść za pomocą podtypów lub narzędzia ADT zawierającego wszystkie różne typy błędów jako konstruktorów (to drugie podejście jest zwykle stosowane w Haskell).
Mimo wszystko wolę podejście Maybe
/, Either
ponieważ uważam, że jest prostsze i bardziej elastyczne.
OpenFile()
może rzucać FileNotFound
lub NoPermission
lub TooManyDescriptors
itp Żaden nie prowadzi tej informacji.if None return None
instrukcji typu.Co najważniejsze, wyjątek i monada Może mają różne cele - wyjątek jest używany do oznaczenia problemu, a być może nie.
„Siostro, jeśli w pokoju 5 jest pacjent, czy możesz poprosić go, aby zaczekał?”
(zauważ „jeśli” - to znaczy, że lekarz spodziewa się monady Może)
None
wartości można po prostu propagować). Twój punkt 5 jest tylko rodzajem racji… pytanie brzmi: które sytuacje są jednoznacznie wyjątkowe? Jak się okazuje… nie wielu .
bind
w taki sposób, że testowanie pod kątem None
nie powoduje jednak narzutu składniowego. Bardzo prosty przykład, C # po prostu odpowiednio przeciąża Nullable
operatorów. Nie jest None
konieczne sprawdzanie , nawet przy użyciu tego typu. Oczywiście kontrola jest nadal wykonywana (jest bezpieczna pod względem typu), ale za kulisami i nie zaśmieca kodu. To samo dotyczy w pewnym sensie twojego sprzeciwu wobec mojego sprzeciwu wobec (5), ale zgadzam się, że nie zawsze może mieć zastosowanie.
Maybe
jako monady polega na upowszechnieniu propagacji None
. Oznacza to, że jeśli chcesz zwrócić None
dane dane None
, nie musisz pisać żadnego specjalnego kodu. Musisz tylko dopasować, jeśli chcesz zrobić coś wyjątkowego None
. Nigdy nie potrzebujesz if None then None
czegoś w rodzaju oświadczeń.
null
sprawdzanie dokładnie tak (np. if Nothing then Nothing
) Za darmo, ponieważ Maybe
jest monadą. Jest zakodowany w definicji bind ( >>=
) dla Maybe
.
Either
), Która zachowuje się tak samo Maybe
. Przełączanie między nimi jest w rzeczywistości dość proste, ponieważ Maybe
tak naprawdę jest to szczególny przypadek Either
. (W Haskell można by pomyśleć Maybe
jako Either ()
.)
„Może” nie zastępuje wyjątków. Wyjątki mają być stosowane w wyjątkowych przypadkach (na przykład: otwieranie połączenia z bazą danych, a serwera db nie ma, chociaż powinno być). „Może” służy do modelowania sytuacji, w której możesz mieć lub nie mieć prawidłową wartość; powiedzmy, że otrzymujesz wartość ze słownika dla klucza: może istnieć lub nie - nie ma nic „wyjątkowego” w żadnym z tych wyników.
Popieram odpowiedź Tichona, ale myślę, że jest bardzo ważny praktyczny punkt, którego wszyscy brakuje:
Either
Mechanizm nie jest sprzężony z gwintem na wszystkich.Tak więc w dzisiejszych czasach widzimy, że wiele rozwiązań programowania asynchronicznego przyjmuje wariant Either
stylu obsługi błędów. Zastanów się nad obietnicami Javascript , jak szczegółowo opisano w dowolnym z tych linków:
Koncepcja obietnic pozwala pisać kod asynchroniczny w następujący sposób (wzięty z ostatniego linku):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Zasadniczo obietnica to obiekt, który:
Zasadniczo, ponieważ natywna obsługa wyjątków języka nie działa, gdy obliczenia odbywają się w wielu wątkach, implementacja obietnic musi zapewniać mechanizm obsługi błędów, a te okazują się być monadami podobnymi do typów Maybe
/ Either
typów Haskella .
System typu Haskell będzie wymagał od użytkownika potwierdzenia możliwości a Nothing
, podczas gdy języki programowania często nie wymagają wychwycenia wyjątku. Oznacza to, że w czasie kompilacji będziemy wiedzieć, że użytkownik sprawdził, czy nie wystąpił błąd.
throws NPE
do każdej podpisu i catch(...) {throw ...}
do każdej treści metody. Ale wierzę, że istnieje rynek dla sprawdzanych w tym samym sensie, co z Może: zerowanie jest opcjonalne i śledzone w systemie typów.
Być może monada jest w zasadzie taka sama, jak w przypadku głównego języka stosowanie sprawdzania „null oznacza błąd” (z tym że wymaga sprawdzenia wartości null) i ma w dużej mierze te same zalety i wady.
Maybe
liczby, pisząc a + b
bez konieczności sprawdzania None
, a wynik jest ponownie wartością opcjonalną.
Maybe
typu , ale użycie Maybe
jako monady dodaje cukier składniowy, który pozwala na bardziej eleganckie wyrażanie logiki zerowej.
Obsługa wyjątków może być prawdziwym problemem dla faktoringu i testowania. Wiem, że Python zapewnia ładną składnię „z”, która pozwala wychwytywać wyjątki bez sztywnego bloku „try ... catch”. Ale na przykład w Javie wypróbuj bloki catch, które są duże, pełne, pełne lub bardzo szczegółowe i trudne do rozbicia. Ponadto Java dodaje cały szum wokół zaznaczonych i niesprawdzonych wyjątków.
Jeśli zamiast tego twoja monada wyłapuje wyjątki i traktuje je jako właściwość przestrzeni monadycznej (zamiast jakiejś anomalii przetwarzania), możesz dowolnie mieszać i dopasowywać funkcje powiązane z tym obszarem, niezależnie od tego, co rzucą lub złapią.
Jeśli, jeszcze lepiej, twoja monada zapobiega warunkom, w których mogłyby się zdarzyć wyjątki (np. Wypchnięcie czeku zerowego do Może), to jeszcze lepiej. jeśli ... to jest dużo, dużo łatwiej wziąć pod uwagę i przetestować niż spróbować ... złapać.
Z tego, co widziałem, Go przyjmuje podobne podejście, określając, że każda funkcja zwraca (odpowiedź, błąd). To trochę tak samo, jak „podniesienie” funkcji do przestrzeni monady, w której typ odpowiedzi na rdzeń jest ozdobiony wskazaniem błędu i skutecznie rzuca na bok wyjątki.