简体   繁体   English

为什么rank-n类型需要明确的forall量词?

[英]Why are explicit forall quantifiers necessary for rank-n types?

When I declare this newtype: 当我声明这个新类型时:

newtype ListScott a = 
  ListScott { 
  unconsScott :: (a -> ListScott a -> r) -> r -> r 
}

which would define the hypothetical rank-2 type ListScott :: ((a -> ListScott a -> r) -> r -> r) -> ListScott a , the compiler complains about r not being in scope. 这将定义假设的rank-2类型ListScott :: ((a -> ListScott a -> r) -> r -> r) -> ListScott a ,编译器抱怨r不在范围内。 Isn't it apparent from the type signature that I want to pass a first class polymorphic function to ListScott ? 从类型签名中我不想将第一类多态函数传递给ListScott吗?

Why do I need an explicit type quantifier for r for cases like this? 为什么我这样的情况需要r的显式类型量词?

I am not a type theorist and have probably overlooked something... 我不是一个类型理论家,可能忽略了一些东西......

This is a question of programming language design. 这是编程语言设计的问题。 It could be inferred in the way you suggest, but I argue that it is a bad idea. 它可以用你建议的方式推断,但我认为这是一个坏主意。

Isn't it apparent from the type signature that I want to pass a first class polymorphic function to ListScott? 从类型签名中我不想将第一类多态函数传递给ListScott吗?

I don't think that we can obviously tell that much from this definition. 我不认为我们可以从这个定义中明显地说出这么多。

Universal or existential? 普遍存在还是存在? Conflict with GADT notation 与GADT表示法冲突

Here's something we can write with the GADTs extension: 这是我们可以使用GADTs扩展编写的GADTs

data ListScott a where
  ListScott :: { unconsScott :: (a -> ListScott a -> r) -> r -> r } -> ListScott a

Here r is quantified existentially in the unconsScott field, so the constructor has the first type below: 这里runconsScott字段中存在量化,因​​此构造函数具有以下第一种类型:

ListScott :: forall a r. ((a -> ListScott a -> r) -> r -> r) -> ListScott a
-- as opposed to
ListScott :: forall a. (forall r. (a -> ListScott a -> r) -> r -> r) -> ListScott a

Inference disables error detection 推断禁用错误检测

What if r is instead meant to be a parameter to ListScott , but we simply forgot to add it? 如果r代替ListScott的参数ListScott ,但我们只是忘了添加它? I believe that is a reasonably probable mistake, because both the hypothetical ListScott ra and ListScott a can serve as representations of lists in some ways. 我认为这是一个合理可能的错误,因为假设的ListScott raListScott a都可以在某些方面用作列表的表示。 Then inference of binders would result in an erroneous type definition being accepted, and errors being reported elsewhere, once the type gets used (hopefully not too far, but that would still be worse than an error at the definition itself). 然后推断绑定器将导致错误的类型定义被接受,并且一旦类型被使用就会在其他地方报告错误(希望不会太远,但是仍然会比定义本身的错误更糟糕)。

Explicitness also prevents typos where a type constructor gets mistyped as a type variable: 显式性还可以防止类型构造函数被错误输入为类型变量的拼写错误:

newtype T = T { unT :: maybe int }
-- unlikely to intentionally mean "forall maybe int. maybe int"

There is not enough context in a type declaration alone to confidently guess the meaning of variables, thus we should be forced to bind variables properly. 仅在类型声明中没有足够的上下文来自信地猜测变量的含义,因此我们应该被迫正确绑定变量。

Readability 可读性

Consider record of functions: 考虑功能记录:

data R a = R
  { f :: (r -> r) -> r -> r
  , g :: r -> r
  }

data R r a = R
  { f :: (r -> r) -> r -> r
  , g :: r -> r
  }

We have to look to the left of = to determine whether r is bound there, and if not we have to mentally insert binders in every field. 我们必须查看=的左边以确定r是否绑定在那里,如果不是,我们必须在每个字段中精神上插入绑定器。 I find that makes the first version hard to read because the r variable in the two fields would actually not be under the same binder, but it certainly looks otherwise at a glance. 我发现这使得第一个版本难以阅读,因为两个字段中的r变量实际上不在同一个活页夹下,但它看起来一目了然。

Comparison to similar constructs 与类似结构的比较

Note that something similar to what you suggested happens with type classes, which can be seen as a sort of record: 请注意类似于您建议的类型类,可以看作是一种记录:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Most arguments above apply as well, and I would thus prefer to write that class as: 上面的大多数论点都适用,因此我更愿意将该类编写为:

class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b

A similar thing could be said of local type annotations. 类似的东西可以说是本地类型注释。 However, top-level signatures are different: 但是,顶级签名是不同的:

id :: a -> a

That unambiguously means id :: forall a. a -> a 这毫不含糊地意味着id :: forall a. a -> a id :: forall a. a -> a , because there is no other level where a could be bound. id :: forall a. a -> a ,因为没有其他级别a可能的约束。

The point is that the constructor would not have the rank-1 (yes, one) type you mention: (quantifiers added for clarity) 关键是构造函数不会提到你提到的rank-1(yes,one)类型:(为了清晰起见,添加了量词)

ListScott1 :: forall a r. ((a -> ListScott a -> r) -> r -> r) -> ListScott a

but the following rank-2 type 但是以下等级-2类型

ListScott2 :: forall a. (forall r. (a -> ListScott a -> r) -> r -> r) -> ListScott a

So, rank-2 are indeed involved in the type checking of your program. 因此,rank-2确实参与了程序的类型检查。

Note that, if f :: (Bool -> ListScott Bool -> Char) -> Char -> Char , then the first constructor above would make ListScott1 f :: ListScott Bool well-typed, but this is not what we want. 注意,如果f :: (Bool -> ListScott Bool -> Char) -> Char -> Char ,那么上面的第一个构造函数会使ListScott1 f :: ListScott Bool打字很好,但这不是我们想要的。 Indeed, using the second constructor, ListScott2 f is ill-typed. 实际上,使用第二个构造函数, ListScott2 f是错误的类型。

Indeed, for ListScott2 f :: ListScott Bool to be well-typed, we need a polymorphic f , having type f :: forall r. (Bool -> ListScott Bool -> r) -> r -> r 实际上,对于ListScott2 f :: ListScott Bool是良好类型的,我们需要一个多态f ,类型为f :: forall r. (Bool -> ListScott Bool -> r) -> r -> r f :: forall r. (Bool -> ListScott Bool -> r) -> r -> r . f :: forall r. (Bool -> ListScott Bool -> r) -> r -> r

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM