13

我正在阅读 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;
                   .... }

这里出了什么问题?为什么编译不出来?

4

2 回答 2

27

在 RWH 出现后不久,异常接口被更改为支持更灵活的处理程序,其中处理程序的类型决定了它将捕获哪些异常。例如,一个处理程序SomeException会捕获任何东西(通常不是一个好主意),而一个处理程序IOException只会捕获 IO 异常。

因此,使用示例中的“无操作”处理程序很容易遇到歧义问题,因为编译器无法推断您要捕获的异常类型。解决此问题的一种简单方法是为您的处理程序函数提供类型签名。

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 ...
于 2012-05-14T07:35:45.917 回答
4

RWH 比较老了。handle函数签名在 GHC 6.10 左右发生了变化。

要使用旧版本,请导入Control.OldException而不是 Control.Exception`。您将收到弃用警告,但程序将编译。

或者您可以使用新接口并为处理程序提供显式签名,如下所示:

((\ _ -> return Nothing) :: IOException -> IO (Maybe Integer))
于 2012-05-14T07:14:44.643 回答