高阶函数内的多态性?

发布于 2024-11-29 08:03:22 字数 1344 浏览 0 评论 0原文

我有一个代数数据类型,其中一些构造函数保存可比较的值,而另一些构造函数则不保存。我编写了一些比较函数,它们的工作方式类似于标准 (==)(/=) 运算符,但对于不这样做的比较返回 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 显然无法绑定到两个 Inthelper的定义中同时使用code>和Float; GHC 将其绑定到 Float,然后抱怨处理 IntValue 的行上的类型不匹配。

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

I have an algebraic data type with some constructors that hold comparable values, and some constructors that don't. I wrote some comparison functions that work like the standard (==) and (/=) operators, but return Nothing for comparisons that don't make sense:

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

That works, but the repetition is unwieldy — especially since I actually have more Variant constructors and more comparison functions.

I thought I could factor out the repetition into a helper function that's parameterized on the comparison function:

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 (/=)

but that doesn't work because the type variable a apparently can't bind to both Int and Float at the same time in the definition of helper; GHC binds it to Float and then complains about a type mismatch on the line that handles IntValue.

A function like (==) is polymorphic when used directly; is there a way to pass it to another function and have it remain polymorphic?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

冷情 2024-12-06 08:03:22

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

{-# 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 实例的所有类型 a 是多态的,这正是您想要的。

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

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

Yes, this is possible, but only with 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

The forall a. does about what it sounds like; the a is universally quantified within the parentheses, and out of scope outside them. This means that the f argument is required to be polymorphic over all types a that are instances of Eq, which is exactly what you want.

The extension here is called "rank 2" because it allows the regular style of polymorphism at the outermost scope, plus polymorphic arguments as in the example here. To nest things further, you need the extension RankNTypes, which is fairly self-descriptive.

As an aside, regarding higher-rank polymorphic types--keep in mind that the forall is what actually binds the variable to a type; you can think of them as behaving a lot like a lambda, in fact. When you apply such a function to something with a concrete type, the type of the argument is implicitly bound by the forall for that use. This comes up, for instance, if you try to use a value whose type was bound by an inner forall outside that function; the value's type has gone out of scope, which makes it difficult to do anything sensible (as you can probably imagine).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文