在Haskell中是否可以定义自定义的守卫机制?

6
如果您查看catches的示例:
 f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
                     Handler (\ (ex :: IOException)    -> handleIO    ex)]

看起来catches定义了一个自定义机制来匹配模式(两种异常类型)。我错了吗?或者这可以概括为允许定义一个函数,该函数可以接受能够匹配某个模式的lambda函数吗?

编辑:FYI下面是GHC catches的源代码。如果有人能够解释一下它是如何工作的,那就太好了。

catches :: IO a -> [Handler a] -> IO a
catches io handlers = io `catch` catchesHandler handlers

catchesHandler :: [Handler a] -> SomeException -> IO a
catchesHandler handlers e = foldr tryHandler (throw e) handlers
    where tryHandler (Handler handler) res
              = case fromException e of
                Just e' -> handler e'
                Nothing -> res
2个回答

6
这是使用作用域类型变量 GHC扩展的示例。点击链接了解更多信息。
基本上,您需要定义有关类型的断言,以便在匹配之前必须满足该模式。因此,是类似于保护,但并非完全如此。
这个特定示例是如何工作的?深入“base”库的源代码来了解:
class (Show e) => Exception e where
    toException   :: e -> SomeException
    fromException :: SomeException -> Maybe e

data SomeException = forall e . Exception e => SomeException e

instance Exception IOException where
    toException = IOException
    fromException (IOException e) = Just e
    fromException _ = Nothing

instance Exception ArithException where
    toException = ArithException
    fromException (ArithException e) = Just e
    fromException _ = Nothing

我们可以看到,IOExceptionArithException是实现了类型类Exception的不同类型。我们还可以看到,toException/fromException是一种包装/解包机制,它允许我们将Exception类型的值转换为IOExceptionArithException等类型的值,反之亦然。
因此,我们可以这样写:
f = expr `catches` [Handler handleArith,
                    Handler handleIO]

handleArith :: ArithException -> IO ()
handleArith ex = ....

handleIO :: IOException -> IO ()
handleIO ex = ....

假设发生了IOException异常。当catchesHandler处理处理程序列表的第一个元素时,它调用tryHandler,tryHandler又调用fromException。从tryHandler的定义中可以得出,fromException的返回类型应该与handleArith的参数类型相同。另一方面,e的类型是Exception,即IOException...。因此,类型的作用如下(这不是有效的Haskell代码,但我希望你能理解我的意思):
fromException :: (IOException ...) -> Maybe ArithException

instance Exception IOException ... 可以立即得出结果为 Nothing,因此该处理程序被跳过。同样的推理,下一个处理程序将被调用,因为 fromException 将返回 (Just (IOException ...))
因此,您使用了 handleArithhandleIO 的类型签名来指定何时调用每个函数,fromException/toException 确保以这种方式发生。
如果您想要的话,您还可以在 f 的定义中限制 handleIOhandleArith 的类型,使用作用域类型变量。可以说,这可以提高可读性。
最后,作用域类型变量在这里不是主要角色。它们只是为了方便而使用的。玩弄这种技巧的主要机器是 fromException/toException 和其它相关函数。作用域类型变量只允许您拥有更接近于 guard patterns 的语法。

作用域类型变量如何涉及?我在捕获源代码中没有看到这种机制的运作。 - me2

1
case () of 
  ()| foo expr1 -> handleFooCase
    | bar expr2 -> handleBarCase
    | otherwise -> blah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接