Не удается перехватить исключение, вызванное «чистой» функцией

#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 в своем ответе. Теперь все исправлено.