Хаскеллеры очень стараются избегать генерации исключений из функции - функция должна генерировать исключение только в действительно исключительных случаях, то есть в ситуациях «этого никогда не должно происходить», например, когда пользователь передает какой-либо ввод, который явно запрещен документацией. Если бы чистые функции регулярно генерировали исключения, это было бы большой проблемой не только потому, что тип не говорит, что нужно быть готовым к перехвату, а также одно исключение перехвата не могу внутри чистой функции, но только в коде IO
, который вызывает функцию. И даже если вы в принципе можете перехватить исключение, может быть трудно предсказать куда, что это нужно сделать, потому что ленивое вычисление может задержать момент, когда это действительно произойдет.
На самом деле, даже в случае, например, Wreq.get
, из функции не генерируется исключение.
Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" `seq` "Ok"
"Ok"
Это действие IO
, который выдает, когда вы его выполняете:
Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" >> pure ()
*** Exception: InvalidUrlException "htt:/p/this-isn't%20even%20valid%20URL%20syntax" "Invalid scheme"
Теперь с экшеном IO
ситуация немного иная. Много действий IO
может иметь потенциально очень разные ошибки в разных ситуациях, которые трудно или невозможно предсказать, например, сбой жесткого диска. Каталогизация всех возможных ошибок в подходящем типе данных для каждого действия была бы серьезной задачей, и было бы действительно довольно громоздко обрабатывать все возможные случаи или выяснять, какие части просто передать. А простая упаковка результата каждого отдельного действия IO
в Maybe
приведет к аналогичной ситуации, как в Java, где каждая ссылка может иметь значение null. Это вам ни о чем не говорит, и люди часто не могут придумать разумных способов справиться с этим.
Это в значительной степени проблема, почему исключения были изобретены в первую очередь, и это справедливо как для процедурных языков, так и для Haskell (или, скорее, это процедурный eDSL, который называется IO
). И поскольку в отличие от чистых функций, IO
имеет четко определенную временную последовательность в потоке управления, также довольно ясно, где вы должны это делать, если вам нужно поймать какое-то конкретное исключение.
Это не значит, что для действия IO
никогда не имеет смысла возвращать значение Maybe
/ Either
, которое делает возможные ошибки явными, просто это не всегда имеет смысл.
Наверное, следует добавить, что вы можете легко проигнорировать возвращаемое значение Left
, если действие IO
вернуло Either
вместо выброса. Но если вы проигнорируете сгенерированное исключение, ваша программа выйдет из строя. Хорошо, конечно, вы также можете игнорировать исключение, но вам нужно больше работать над этим, можно возразить. Но, как я сказал ранее, это, вероятно, тоже будет очень самоуверенным.
FWIW, моя любимая теория состоит в том, что причина, по которой исключения были изобретены / популярны в процедурных языках, заключается в том, что традиционные процедурные языки делают представление, создание и использование типов суммы настолько болезненным; Мне часто хотелось, чтобы конструкция на C была такой же синтаксически дешевой, как Either, например
@Krom, если вы проигнорируете возвращаемое значение Left
, вы тем самым превратите (надеюсь) описательное сообщение об ошибке в непостижимое исключение исчерпывающих шаблонов. И вылететь из программы с помощью что. Если вы проигнорируете исключение, созданное самим действием, оно выйдет из строя с (опять же, с надеждой) описательным сообщением об исключении.
В некоторых случаях (например, с пакетом http) я думаю, что действительно имеет смысл различать условия, которые вызывающий почти наверняка хочет обрабатывать локально (тайм-аут сети, страница не найдена и т. д.), И те, которые с большей вероятностью перейдут в обработчик более высокого уровня (исключения, созданные другим потоком, несуществующий сетевой интерфейс и т. д.).
@dfeuer Я согласен, но может быть сложно где-то провести черту. И я подозреваю, что тип результата, содержащий ошибку, снизит чувствительность людей, и они также могут забыть о перехвате исключений.
@leftaroundabout, это определенно суждение. Но для исключений много единственная разумная вещь, которую можно сделать в приложениях самый, - позволить ему пройти весь путь вверх и убить поток. Пользователи всегда несут ответственность за то, чтобы их программы регистрировали / уведомляли надлежащим образом, когда что-то неожиданно умирает.
Кому-то понравится то же самое, кому-то понравятся их исключения. Боюсь, это очень самоуверенно.