Подтвердить что ты не робот

Функция "handle" и Real World Haskell

Я читаю RWH, и я пришел к главе 9. В ней представлен следующий фрагмент кода:

import System.IO
import Control.Exception

saferFileSize :: FilePath -> IO (Maybe Integer)
saferFileSize path = handle (\_ -> return Nothing) $ do
  h <- openFile path ReadMode
  size <- hFileSize h
  hClose h
  return (Just size)

Однако он не будет компилироваться, указав следующее сообщение об ошибке:

test.hs:5:22:
    Ambiguous type variable `e0' in the constraint:
      (Exception e0) arising from a use of `handle'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: handle (\ _ -> return Nothing)
    In the expression:
      handle (\ _ -> return Nothing)
      $ do { h <- openFile path ReadMode;
             size <- hFileSize h;
             hClose h;
             return (Just size) }
    In an equation for `saferFileSize':
        saferFileSize path
          = handle (\ _ -> return Nothing)
            $ do { h <- openFile path ReadMode;
                   size <- hFileSize h;
                   hClose h;
                   .... }

Что здесь происходит? Почему он не компилируется?

4b9b3361

Ответ 1

Не слишком долго после выхода RWH интерфейс исключений был изменен для поддержки более гибких обработчиков, где тип обработчика определяет, какие исключения он будет ловить. Например. обработчик, который принимает SomeException, поймает что-нибудь (обычно это не очень хорошая идея), в то время как обработчик, который принимает IOException, будет захватывать только исключения IO.

В результате этого легко столкнуться с проблемами двусмысленности с обработчиками do-nothing, такими как в вашем примере, поскольку компилятор не может определить, какие исключения вы пытаетесь поймать. Легкий способ исправить это - предоставить сигнатуру типа для вашей функции обработчика.

handle ((\_ -> return Nothing) :: IOException -> IO (Maybe Integer)) $ do ...

Хотя, это может быть несколько многословным. Альтернативным решением является специализация handle.

handleIO :: (IOException -> IO a) -> IO a -> IO a
handleIO = handle

Затем вы можете просто использовать handleIO всякий раз, когда хотите обрабатывать исключения IO, без необходимости указывать подпись типа обработчика.

saferFileSize path = handleIO (\_ -> return Nothing) $ do ...

Третий вариант - использовать расширение ScopedTypeVariables, которое (среди прочего) позволяет вам предоставлять аннотацию типа только для аргумента функции, позволяя остальным быть выведенным.

{-# LANGUAGE ScopedTypeVariables #-}
saferFileSize path = handle (\(_ :: IOException) -> return Nothing) $ do ...

Ответ 2

RWH довольно старый. Функциональная подпись handle изменилась в GHC 6.10 или около того.

Чтобы использовать старую версию, импортируйте Control.OldException вместо Control.Exception`. Вы получите предупреждения об устаревании, но программа будет компилироваться.

Или вы можете использовать новый интерфейс и дать обработчику явную подпись, например:

((\ _ -> return Nothing) :: IOException -> IO (Maybe Integer))