Dobra, pierwsza zasada obsługi błędów w Haskell: Nigdy nie używajerror
.
To jest po prostu okropne pod każdym względem. Istnieje wyłącznie jako akt historii, a fakt, że używa go Preludium, jest okropny. Nie używaj tego.
Jedyny możliwy czas, w którym można go użyć, to gdy coś jest tak wewnętrznie okropne, że coś musi być nie tak z samą strukturą rzeczywistości, co powoduje, że wyniki programu są dyskusyjne.
Teraz pojawia się pytanie Maybe
vs Either
. Maybe
jest dobrze dostosowany do czegoś takiego head
, co może, ale nie musi zwrócić wartość, ale istnieje tylko jeden możliwy powód niepowodzenia. Nothing
mówi coś takiego: „zepsuło się, a ty już wiesz dlaczego”. Niektórzy twierdzą, że oznacza to funkcję częściową.
Najbardziej niezawodną formą obsługi błędów jest Either
+ ADT błąd.
Na przykład w jednym z moich kompilatorów hobby mam coś takiego
data CompilerError = ParserError ParserError
| TCError TCError
...
| ImpossibleError String
data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
| MissingDefinition Name
| InfiniteType Ty
...
type ErrorM m = ExceptT CompilerError m -- from MTL
Teraz definiuję kilka rodzajów błędów, zagnieżdżając je tak, że mam jeden wspaniały błąd najwyższego poziomu. Może to być błąd na dowolnym etapie kompilacji lub błąd ImpossibleError
, który oznacza błąd kompilatora.
Każdy z tych typów błędów stara się przechowywać jak najwięcej informacji tak długo, jak to możliwe, aby uzyskać ładny wydruk lub inną analizę. Co ważniejsze, nie mając łańcucha, mogę przetestować, że uruchomienie niepoprawnego programu przez moduł sprawdzania typów faktycznie generuje błąd unifikacji! Kiedy coś jest String
, zniknie na zawsze, a wszelkie zawarte w nim informacje są nieprzejrzyste dla kompilatora / testów, więc też Either String
nie jest świetne.
Na koniec pakuję ten typ w ExceptT
nowy transformator monadowy od MTL. Jest to zasadniczo EitherT
i zawiera niezłą porcję funkcji do rzucania i wyłapywania błędów w czysty, przyjemny sposób.
Na koniec warto wspomnieć, że Haskell ma mechanizmy obsługi wyjątków, tak jak inne języki, z wyjątkiem tego, że łapanie wyjątku trwa IO
. Wiem, że niektórzy ludzie lubią używać ich do IO
ciężkich aplikacji, w których wszystko może potencjalnie zawieść, ale tak rzadko, że nie lubią o tym myśleć. Niezależnie od tego, czy korzystasz z tych nieczystych wyjątków, czy po prostu ExceptT Error IO
jest to kwestia gustu. Osobiście wybieram, ExceptT
ponieważ lubię przypominać sobie o szansie niepowodzenia.
Podsumowując
Maybe
- Mogę zawieść w jeden oczywisty sposób
Either CustomType
- Mogę zawieść, a powiem ci, co się stało
IO
+ wyjątki - czasami mi się nie udaje. Sprawdź moje dokumenty, aby zobaczyć, co rzucam, kiedy to robię
error
- Ja też cię nienawidzę
head
lublast
wydają się używaćerror
, więc zastanawiałem się, czy to był dobry sposób na zrobienie czegoś i po prostu czegoś mi brakowało. To odpowiada na to pytanie. :)Nie rozdzieliłbym 2 i 3 na podstawie tego, na ile sposobów coś może zawieść. Gdy tylko pomyślisz, że jest tylko jeden możliwy sposób, inny pokaże swoją twarz. Metryka powinna brzmieć: „czy moim rozmówcom zależy na tym, dlaczego coś się nie udało?
Poza tym nie jest dla mnie od razu jasne, że
Either String SomeType
może powodować wystąpienie błędu. Zrobiłbym prosty algebraiczny typ danych z warunkami o bardziej opisowej nazwie.To, którego używasz, zależy od charakteru napotkanego problemu i idiomów pakietu oprogramowania, z którym pracujesz. Chociaż starałbym się unikać # 1.
źródło
Either
błędów jest bardzo dobrze znanym wzorcem w Haskell.Either
nie jest niejasną częścią, użycieString
jako błędu, a nie rzeczywistego ciągu.