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

时间:2018-01-12 11:40:57

标签: haskell types higher-rank-types rank-n-types scott-encoding

当我宣布这个新类型时:

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

将定义假设的rank-2类型ListScott :: ((a -> ListScott a -> r) -> r -> r) -> ListScott a,编译器抱怨r不在范围内。从类型签名中可以看出,我想将第一类多态函数传递给ListScott吗?

对于像这样的情况,为什么我需要r的显式类型量词?

我不是一个类型的理论家,可能忽略了一些东西......

2 个答案:

答案 0 :(得分:12)

这是编程语言设计的问题。它可以用你建议的方式推断,但我认为这是一个坏主意。

  

从类型签名中我不想将第一类多态函数传递给ListScott吗?

我不认为我们可以从这个定义中明显地说出这么多。

普遍存在还是存在?与GADT符号冲突

以下是我们可以使用GADTs扩展程序编写的内容:

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

此处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

推断禁用错误检测

如果r意味着是ListScott的参数,但我们只是忘了添加它,该怎么办?我认为这是一个合理可能的错误,因为假设ListScott r aListScott a都可以在某些方面作为列表的表示。然后推断绑定器会导致错误的类型定义被接受,并且一旦使用了类型,就会在其他地方报告错误(希望不会太远,但这仍然会比定义本身的错误更糟)。

显式性还可以防止类型构造函数被错误输入的类型变量的错别字:

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

单独的类型声明中没有足够的上下文来自信地猜测变量的含义,因此我们应该被迫正确绑定变量。

可读性

考虑功能记录:

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
  }

我们必须查看=的左侧以确定r是否绑定在那里,如果不是,我们必须在每个字段中精神上插入绑定器。我发现这使得第一个版本难以阅读,因为两个字段中的r变量实际上不在同一个活页夹下,但它看起来一目了然。

与类似构造的比较

请注意类似于您所建议的类型类,可以看作是一种记录:

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

上面的大多数论点都适用,因此我更愿意将该类编写为:

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

类似的东西可以说是本地类型注释。但是,顶级签名是不同的:

id :: a -> a

明确指出id :: forall a. a -> a,因为没有其他级别可以绑定a

答案 1 :(得分:4)

关键是构造函数不会提到你提到的rank-1(yes,one)类型: (为了清晰起见,添加了量词)

where

但是以下等级-2类型

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

因此,rank-2确实参与了程序的类型检查。

请注意,如果ListScott2 :: forall a. (forall r. (a -> ListScott a -> r) -> r -> r) -> ListScott a ,则上面的第一个构造函数会使f :: (Bool -> ListScott Bool -> Char) -> Char -> Char输入良好,但这不是我们想要的。实际上,使用第二个构造函数ListScott1 f :: ListScott Bool是错误的类型。

事实上,要使ListScott2 f输入良好,我们需要一个多态ListScott2 f :: ListScott Bool,其类型为f