Haskell 存在量化详解

发布于 2025-01-04 15:45:26 字数 313 浏览 6 评论 0原文

我对类型的存在量化是什么以及它可以在哪里使用有一个大致的了解。然而,根据我迄今为止的经验,为了有效地使用这个概念,需要理解很多注意事项。

问题:是否有任何好的资源可以解释 GHC 中如何实施存在量化?即

  • 存在类型的统一是如何运作的——什么是统一的,什么不是?
  • 对类型的后续操作按什么顺序执行?

我的目标是更好地理解 GHC 向我抛出的错误消息。消息通常会说一些类似的内容“使用 forall 的此类型与其他类型不匹配”,但是它们没有解释为什么会这样。

I have a general idea of what existential quantification on types is and where it can be used. However from my experiences so far, there are a lot of caveats that need to be understood in order to use the concept effectively.

Question: Are there any good resources explaining how existential quantification is implemented in GHC? I.e.

  • How does unification on existential types work - what is unifiable and what is not?
  • In what order subsequent operations on types are performed?

My aim is to better understand error messages that GHC throws at me. Messages usually say something along the lines "this type using forall and this other type don't match", however they don't explain why it is so.

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

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

发布评论

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

评论(1

jJeQQOZ5 2025-01-11 15:45:26

西蒙·佩顿·琼斯 (Simon Peyton-Jones) 的论文涵盖了具体细节,但需要大量的技术专业知识才能理解它们。如果您想阅读有关 Haskell 类型推断如何工作的论文,您应该阅读广义代数数据类型 (GADT),它将存在类型与其他几个功能相结合。我建议“GADT 的完整且可判定的类型推断”,位于 http://research.microsoft 的论文列表中.com/en-us/people/simonpj/

存在量化实际上很像通用量化。这是一个突出两者之间相似之处的例子。函数useExis没有用,但它仍然是有效的代码。

data Univ a = Univ a
data Exis = forall a. Exis a

toUniv :: a -> Univ a
toUniv = Univ

toExis :: a -> Exis
toExis = Exis

useUniv :: (a -> b) -> Univ a -> b
useUniv f (Univ x) = f x

useExis :: (forall a. a -> b) -> Exis -> b
useExis f (Exis x) = f x

首先,请注意 toUnivtoExis 几乎相同。它们都有一个自由类型参数a,因为两个数据构造函数都是多态的。但是,虽然 a 出现在 toUniv 的返回类型中,但它却没有出现在 toExis 的返回类型中。当谈到使用数据构造函数可能出现的类型错误时,存在类型和通用类型之间没有太大区别。

其次,注意所有 a 的 2 级类型 。一个-> useExis 中的 b。这是类型推断的巨大差异。从模式匹配 (Exis x) 中获取的存在类型就像传递到函数体的额外隐藏类型变量,并且不得与其他类型统一。为了更清楚地说明这一点,这里是最后两个函数的一些错误声明,我们试图统一不应该统一的类型。在这两种情况下,我们强制 x 的类型与不相关的类型变量统一。在useUniv中,类型变量是函数类型的一部分。在useExis中,它是数据结构中的存在类型。

useUniv' :: forall a b c. (c -> b) -> Univ a -> b
useUniv' f (Univ x) = f x -- Error, can't unify 'a' with 'c'
                          -- Variable 'a' is there in the function type

useExis' :: forall b c. (c -> b) -> Exis -> b
useExis' f (Exis x) = f x -- Error, can't unify 'a' with 'c'.
                          -- Variable 'a' comes from the pattern "Exis x",
                          -- via the existential in "data Exis = forall a. Exis a".

The nitty-gritty details are covered in papers from Simon Peyton-Jones, though it takes a good deal of technical expertise to understand them. If you want to read a paper on how Haskell type inference works, you should read about generalized algebraic data types (GADTs), which combine existential types with several other features. I suggest "Complete and Decidable Type Inference for GADTs", on the list of papers at http://research.microsoft.com/en-us/people/simonpj/.

Existential quantification actually works a lot like universal quantification. Here is an example to highlight the parallels between the two. The function useExis is useless, but it's still valid code.

data Univ a = Univ a
data Exis = forall a. Exis a

toUniv :: a -> Univ a
toUniv = Univ

toExis :: a -> Exis
toExis = Exis

useUniv :: (a -> b) -> Univ a -> b
useUniv f (Univ x) = f x

useExis :: (forall a. a -> b) -> Exis -> b
useExis f (Exis x) = f x

First, note that toUniv and toExis are nearly the same. They both have a free type parameter a because both data constructors are polymorphic. But while a appears in the return type of toUniv, it doesn't appear in the return type of toExis. When it comes to the kind of type errors you might get from using a data constructor, there's not a big difference between existential and universal types.

Second, note the rank-2 type forall a. a -> b in useExis. This is the big difference in type inference. The existential type taken from the pattern match (Exis x) acts like an extra, hidden type variable passed to the body of the function, and it must not be unified with other types. To make this clearer, here are some wrong declarations of the last two functions where we try to unify types that shouldn't be unified. In both cases, we force the type of x to be unified with an unrelated type variable. In useUniv, the type variable is part of the function type. In useExis, it's the existential type from the data structure.

useUniv' :: forall a b c. (c -> b) -> Univ a -> b
useUniv' f (Univ x) = f x -- Error, can't unify 'a' with 'c'
                          -- Variable 'a' is there in the function type

useExis' :: forall b c. (c -> b) -> Exis -> b
useExis' f (Exis x) = f x -- Error, can't unify 'a' with 'c'.
                          -- Variable 'a' comes from the pattern "Exis x",
                          -- via the existential in "data Exis = forall a. Exis a".
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文