16

我有一个代数数据类型,其中一些构造函数具有可比较的值,而一些构造函数则没有。我编写了一些比较函数,它们的工作方式类似于标准(==)(/=)运算符,但返回Nothing用于没有意义的比较:

data Variant = IntValue Int
             | FloatValue Float
             | NoValue

equal :: Variant -> Variant -> Maybe Bool
equal (IntValue a) (IntValue b) = Just (a == b)
equal (FloatValue a) (FloatValue b) = Just (a == b)
equal _ _ = Nothing

unequal :: Variant -> Variant -> Maybe Bool
unequal (IntValue a) (IntValue b) = Just (a /= b)
unequal (FloatValue a) (FloatValue b) = Just (a /= b)
unequal _ _ = Nothing

这行得通,但重复很笨拙——尤其是因为我实际上有更多的Variant构造函数和更多的比较函数。

我想我可以将重复因素分解为一个在比较函数上参数化的辅助函数:

helper :: (Eq a) => (a -> a -> Bool) -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

equal' :: Variant -> Variant -> Maybe Bool
equal' = helper (==)

unequal' :: Variant -> Variant -> Maybe Bool
unequal' = helper (/=)

但这不起作用,因为类型变量a显然不能同时绑定到Int;Float的定义中helper。GHC 将其绑定到Float,然后在处理IntValue.

直接使用时, like 函数(==)是多态的;有没有办法将它传递给另一个函数并让它保持多态?

4

1 回答 1

15

是的,这是可能的,但仅限于语言扩展

{-# LANGUAGE Rank2Types #-}

helper :: (forall a. (Eq a) => (a -> a -> Bool))
       -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

forall a.听起来像什么;thea在括号内被普遍量化,在括号外超出范围。这意味着该f参数必须是所有类型的多态 a 的实例Eq,这正是您想要的。

此处的扩展称为“等级 2”,因为它允许在最外层范围内使用常规样式的多态,以及此处示例中的多态参数。要进一步嵌套,您需要扩展名RankNTypes,这是相当自我描述的。

顺便说一句,关于更高级别的多态类型——请记住,forall实际上将变量绑定到类型;事实上,你可以认为它们的行为很像 lambda。当您将这样的函数应用于具有具体类型的事物时,参数的类型隐含地受forall用于该用途的约束。例如,如果您尝试使用其类型被forall该函数外部的内部绑定的值,就会出现这种情况;该值的类型已经超出范围,这使得做任何明智的事情变得困难(正如您可能想象的那样)。

于 2011-08-15T04:02:50.927 回答