高阶函数中的多态性?

16

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

我有一个代数数据类型,其中一些构造函数持有可比较的值,而另一些构造函数则不持有。我编写了一些比较函数,它们的工作方式类似于标准的(==)(/=)运算符,但对于不合理的比较返回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 显然不能同时绑定到 IntFloat 上,在 helper 的定义中; GHC将其绑定到 Float,然后在处理 IntValue 的行上报告类型不匹配错误。

(==) 这样的函数在直接使用时是多态的;有没有办法将它传递给另一个函数并保持其多态性?

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.的作用即为其听起来的意思; 在括号内部,a被普遍量化,并且在外部超出范围。这意味着f参数必须是多态的,适用于所有类型a的实例,这正是您想要的。
这里的扩展称为“等级2”,因为它允许在最外层范围内使用常规风格的多态性,以及如此示例中的多态参数。为了进一步嵌套事物,您需要使用扩展名为RankNTypes,这相当容易理解。
另外,关于高阶多态类型,请记住forall实际上是将变量绑定到类型的操作;实际上,您可以认为它们的行为非常类似于lambda。当您将这样的函数应用于具有具体类型的东西时,该参数的类型会隐含地由该使用的forall绑定。例如,如果您尝试在该函数之外使用由内部forall绑定类型的值,则该值的类型已经超出了范围,这使得做任何有意义的事情都很困难(您可能可以想象)。

嗯,我有一种感觉答案会涉及到“forall”,而且我实际上已经尝试过几乎完全相同的方法,但是我漏掉了你添加的一对括号。我不太明白为什么需要这些括号;维基上的例子没有使用它们。 - Wyzard
1
@Wyzard:这就是为什么我添加了编辑...想象一下,forall实际上是一个带有额外参数的函数(在幕后也是如此)。括号的区别在于类型是作为helper的参数(默认情况)还是作为f的参数(你想要的)。 - C. A. McCann
1
在编写“类型参数”时,我们在一个例子中使用大括号,如下所示:helper {a} (f :: a -> a -> Bool) (IntValue x) (IntValue y) = Just (f x y),但这是一个类型错误,因为a在外部作用域中(在不同的位置上使用了IntFloat)。在另一个例子中,我们有helper f (IntValue x) (IntValue y) = Just (f {Int} x y),并在另一个位置将f应用于Float,因此类型匹配。 - C. A. McCann

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