我有一个代数数据类型,其中一些构造函数具有可比较的值,而某些构造函数则没有。我编写了一些与标准(==)
和(/=)
运算符类似的比较函数,但返回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
的行上的类型不匹配。
像(==)
这样的函数在直接使用时是多态的;有没有办法将它传递给另一个函数,并保持多态?
答案 0 :(得分:15)
是的,这是可能的,但只能使用language extensions:
{-# 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.
关于听起来是什么感觉; a
在括号内被普遍量化,超出了它们之外的范围。这意味着f
参数必须是Eq
实例的所有类型的多态,这正是您想要的。
此处的扩展名为“rank 2”,因为它允许最外层范围内的常规样式的多态性,以及此处示例中的多态参数。要进一步嵌套,您需要扩展名RankNTypes
,这是相当自我描述的。
另外,关于更高级别的多态类型 - 请记住forall
实际上是将变量绑定到某个类型;事实上,你可以把它们看作很像lambda的行为。当您将此类函数应用于具有特定类型的事物时,参数的类型将由forall
隐式绑定以用于该用途。例如,如果您尝试使用其类型由该函数外部的内部forall
绑定的值,则会出现这种情况;值的类型超出了范围,这使得很难做任何合理的事情(你可以想象)。