Почему исключения Haskell могут быть пойманы только внутри монады ввода-вывода? - PullRequest
27 голосов
/ 04 сентября 2010

Кто-нибудь может объяснить, почему исключения могут быть выброшены за пределы монады IO, но могут быть пойманы только внутри нее?

Ответы [ 3 ]

24 голосов
/ 05 сентября 2010

Одной из причин является денотационная семантика Haskell .

Одним из замечательных свойств (чистых) функций Haskell является их монотонность - более определенный аргумент дает более определенное значение. Это свойство очень важно, например, рассуждать о рекурсивных функциях (прочитайте статью, чтобы понять почему).

Обозначение исключения по определению является нижним, _|_, наименьшим элементом в poset, соответствующем данному типу. Таким образом, чтобы удовлетворить требование монотонности, для любого обозначения f функции Haskell должно выполняться следующее неравенство:

f(_|_) <= f(X)

Теперь, если бы мы могли отлавливать исключения, мы могли бы преодолеть это неравенство, «распознав» дно (перехватив исключение) и вернув более определенное значение:

f x = case catch (seq x True) (\exception -> False) of
        True -> -- there was no exception
            undefined
        False -> -- there was an exception, return defined value
            42

Вот полная рабочая демонстрация (требуется базовое-4 Control.Exception):

import Prelude hiding (catch)
import System.IO.Unsafe (unsafePerformIO)
import qualified Control.Exception as E

catch :: a -> (E.SomeException -> a) -> a
catch x h = unsafePerformIO $ E.catch (return $! x) (return . h)

f x = case catch (seq x True) (\exception -> False) of
        True -> -- there was no exception
            undefined
        False -> -- there was an exception, return defined value
            42

Другая причина, как отметил TomMD, заключается в нарушении ссылочной прозрачности. Вы можете заменить равные вещи равными и получить другой ответ. (Равно в денотационном смысле, т. Е. Они обозначают одно и то же значение, а не в == смысле.)

Как бы мы это сделали? Рассмотрим следующее выражение:

let x = x in x

Это бесконечная рекурсия, поэтому она никогда не возвращает нам никакой информации и поэтому обозначается также _|_. Если бы мы могли отлавливать исключения, мы могли бы написать функцию f, такую ​​как

f undefined = 0
f (let x = x in x) = _|_

(Последнее всегда верно для строгих функций, потому что Haskell не предоставляет средств для обнаружения непрерывных вычислений - и не может в принципе из-за проблемы остановки .)

14 голосов
/ 04 сентября 2010

Потому что исключения могут нарушать ссылочную прозрачность .

Вы, вероятно, говорите об исключениях, которые на самом деле являются прямыми результатами ввода. Например:

head [] = error "oh no!" -- this type of exception
head (x:xs) = x

Если вы жалуетесь, что не можете отловить подобные ошибки, я утверждаю, что функции не должны полагаться на error или любые другие исключения, а вместо этого должны использовать правильный тип возврата (Maybe, Either или, возможно, MonadError ). Это заставляет вас иметь дело с исключительным условием более явным образом.

В отличие от вышеизложенного (и того, что вызывает проблему в вашем вопросе), исключения могут быть из сигналов, таких как нехватка памяти, которые полностью независимы от вычисляемого значения. Это явно не чистая концепция и должна жить в IO.

2 голосов
/ 13 апреля 2016

Я могу ошибаться в своем объяснении, но я так понимаю.

Поскольку в Haskell функции чистые, компилятор имеет право оценивать их в любом порядке, в котором он пожелает, и он по-прежнему выдает то же самое.результат.Например, данная функция:

square :: Int -> Int
square x = x * x

выражение square (square 2) может быть оценено по-разному, но она всегда сводится к одному и тому же результату, который равен 16.

Если мы вызываем squareоткуда-то еще:

test x = if x == 2 
         then square x 
         else 0

square x можно оценить позже, «вне» функции test, когда значение действительно необходимо.В этот момент стек вызовов может полностью отличаться от того, который вы ожидаете сказать в Java.

Так что даже если мы хотим отловить потенциальное исключение, генерируемое square, куда вы должны поместить catchчасть

...