#haskell #exception
#haskell #исключение
Вопрос:
Я пытаюсь перехватить пользовательское исключение, генерируемое «чистым» кодом. Я не понимаю поведения моей программы.
Я использую GHC 8.6.3. Вот мой код:
import Control.Exception
newtype Problem = Problem String deriving Show
instance Exception Problem
foo :: Int -> Int
foo n = n (throw $ Problem "Whoops")
baz :: IO Int
baz =
return (foo 1)
`catch` (Problem _) -> return 100
main = do
n <- baz
print n
`catch` (Problem msg) -> putStrLn msg
Я ожидаю, что исключение будет перехвачено первым обработчиком, и программа выведет «100». Вместо этого оно перехватывается вторым обработчиком и выводит «Упс».
Почему исключение перехватывается в main
, а не в baz
? Как я могу перехватить исключение в baz
?
Ответ №1:
Из-за лени, foo 1
не выполняется до тех пор, пока вы на самом деле не попытаетесь напечатать значение n
. По сути, n
привязано к недооцененному thunk foo 1
, а не к результату foo 1
.
Довольно неуклюжий способ принудительно baz
обработать исключение — это использовать seq
; почти наверняка есть более элегантное решение.
baz = let result = foo 1
in seq result (return result) `catch` (Problem msg) -> return 100
И, благодаря @Alec, это более элегантное решение — просто заменить return
на Control.Exception.evaluate
в вашей исходной функции.
baz :: IO Int
baz =
evaluate (foo 1)
`catch` (Problem _) -> return 100
Комментарии:
1.
seq result (return result)
имеет удобную (немного более эффективную) функцию для этого:evaluate
. Кроме того, нет необходимости в привязке let!evaluate (foo 1) `catch` (Problem msg) -> putStrLn msg
2.
s/almost//
. 🙂3. Упс! Я имел в виду:
baz = evaluate (foo 1) `catch` (Problem msg) -> return 100
. Меня сбило с толку вашеcatch
тело (оно неправильное!).4. @Alec,
evaluate
это не более эффективно; это лучше работает.5. @Alec Ах, да, это было причиной моих проблем при попытке тестирования
evaluate
. Мой тестовый файл был правильным, но затем я слепо скопировал неправильное определениеbaz
в своем ответе. Теперь все исправлено.