【问题标题】:Polymorphism within higher-order functions?高阶函数内的多态性?
【发布时间】:2011-08-15 03:44:43
【问题描述】:

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

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

【问题讨论】:

    标签: haskell types polymorphism higher-order-functions


    【解决方案1】:

    是的,这是可能的,但仅限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 参数必须在所有类型 a 上都是多态的,这些类型是 Eq 的实例,这正是您想要的。

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

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

    【讨论】:

    • 嗯,我感觉答案将涉及forall,实际上我几乎完全尝试过,但我错过了你添加的那对括号。我不太明白为什么这些括号是必要的; examples on the wiki 不要使用它们。
    • @Wyzard:这就是我添加编辑的原因......想象一下forall 实际上是一个采用额外参数的函数(它有点像在幕后)。括号是helper(默认)或f(你想要的)的参数类型之间的区别。
    • 在大括号中写入“类型参数”,在一种情况下我们有helper {a} (f :: a -> a -> Bool) (IntValue x) (IntValue y) = Just (f x y) ,这是一个类型错误,因为a 在外部范围内(在Int 和@ 上使用它987654339@在不同的地方)。在另一个位置我们有helper f (IntValue x) (IntValue y) = Just (f {Int} x y),同样将f 应用到另一个位置的Float,因此类型可以工作。
    • 好的,我想我现在明白了。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-04
    • 2016-05-07
    相关资源
    最近更新 更多