这是工作中的Scoped Type Variables GHC 扩展。点击链接了解更多信息。
基本上,您定义了一个关于模式必须满足的类型才能匹配的断言。所以,是的,它类似于守卫,但并不完全如此。
这个特定的例子是如何工作的?深入了解sources of "base" library 以了解:
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
我们看到IOException 和ArithException 是实现类型类Exception 的不同类型。我们还看到toException/fromException 是一种包装/解包装机制,它允许将Exception 类型的值转换为IOException、ArithException 等类型的值。
所以,我们可以这样写:
f = expr `catches` [Handler handleArith,
Handler handleIO]
handleArith :: ArithException -> IO ()
handleArith ex = ....
handleIO :: IOException -> IO ()
handleIO ex = ....
假设IOException 发生。当catchesHandler 处理处理程序列表的第一个元素时,它调用tryHandler,它调用fromException。根据tryHandler 的定义,fromException 的返回类型应该与handleArith 的参数相同。另一方面,e 属于异常类型,即 - (IOException ...)。所以,类型以这种方式发挥作用(这不是一个有效的 haskell,但我希望你明白我的意思):
fromException :: (IOException ...) -> Maybe ArithException
从instance Exception IOException ... 立即得出结果是Nothing,因此跳过此处理程序。出于同样的原因,将调用以下处理程序,因为fromException 将返回(Just (IOException ...))。
所以,您使用了handleArith 和handleIO 的类型签名来指定它们各自的调用时间,并且fromException/toException 确保它以这种方式发生。
如果您愿意,您还可以使用作用域类型变量在f 的定义中约束handleIO 和handleArith 的类型。可以说,这可以为您提供更好的可读性。
最终,作用域类型变量不是这里的主要参与者。它们只是为了方便而使用。玩这种花样的主要机器是fromException/toException和朋友们。作用域类型变量只允许您使用更类似于保护模式的语法。