Хорошо, первое правило обработки ошибок в Haskell: никогда не используйтеerror
.
Это просто ужасно во всех отношениях. Он существует исключительно как акт истории, и тот факт, что Прелюдия использует его, ужасен. Не используйте это.
Единственное мыслимое время, которое вы можете использовать, это когда что-то настолько внутренне ужасное, что что-то должно быть не так с самой тканью реальности, что делает результат вашей программы спорным.
Теперь вопрос становится Maybe
против Either
. Maybe
хорошо подходит для чего-то подобного head
, которое может возвращать или не возвращать значение, но есть только одна возможная причина для сбоя. Nothing
говорит что-то вроде «это сломалось, и вы уже знаете, почему». Некоторые скажут, что это указывает на частичную функцию.
Самая надежная форма обработки ошибок - Either
+ ошибка ADT.
Например, в одном из моих компиляторов хобби у меня есть что-то вроде
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
Теперь я определяю кучу типов ошибок, вкладывая их так, чтобы у меня была одна великолепная ошибка верхнего уровня. Это может быть ошибка на любом этапе компиляции или ImpossibleError
ошибка, которая означает ошибку компилятора.
Каждый из этих типов ошибок старается как можно дольше хранить информацию для красивой печати или другого анализа. Еще важнее то, что, не имея строки, я могу проверить, что при запуске некорректной программы через средство проверки типов действительно возникает ошибка объединения! Если что-то есть String
, оно исчезает навсегда, и любая содержащаяся в нем информация непрозрачна для компилятора / тестов, так что просто Either String
не слишком хороша.
Наконец, я упаковываю этот тип в ExceptT
новый монадный трансформатор от MTL. Это, по сути, EitherT
и поставляется с отличным набором функций для чистого и приятного вывода ошибок.
Наконец, стоит упомянуть, что на Haskell есть механизмы для поддержки обработки исключений, как это делают другие языки, за исключением того, что перехват исключения находится в IO
. Я знаю, что некоторые люди любят использовать их для IO
тяжелых приложений, где все может потерпеть неудачу, но так редко, что им не нравится думать об этом. Используете ли вы эти нечистые исключения или просто ExceptT Error IO
вопрос вкуса. Лично я выбираю, ExceptT
потому что мне нравится напоминать о вероятности неудачи.
Как итог,
Maybe
- Я могу потерпеть неудачу одним очевидным способом
Either CustomType
- Я могу потерпеть неудачу, и я скажу тебе, что случилось
IO
+ исключения - я иногда терплю неудачу. Проверьте мои документы, чтобы увидеть, что я бросаю, когда я делаю
error
- Я тоже тебя ненавижу, пользователь
head
или,last
кажется, используютerror
, поэтому мне было интересно, действительно ли это хороший способ сделать что-то, и я просто что-то упустил. Это отвечает на этот вопрос. :)