Haskell 如何确定嵌套通用量词的范围(更高级别的类型)?
最初,我假设函数类型LHS上嵌套的通用量词的范围可以纯语法确定,即Haskell 如何确定嵌套通用量词的范围(更高级别的类型)?,haskell,functional-programming,quantifiers,unification,higher-rank-types,Haskell,Functional Programming,Quantifiers,Unification,Higher Rank Types,最初,我假设函数类型LHS上嵌套的通用量词的范围可以纯语法确定,即(对于所有a.a->b)->Bool的括号内的所有内容都在同一范围内。然而,这一假设是错误的: {-# LANGUAGE RankNTypes #-} fun :: (forall a. [a] -> b) -> Bool fun _ = undefined arg :: c -> c arg _ = undefined r = fun arg -- type variable a would escape
(对于所有a.a->b)->Bool
的括号内的所有内容都在同一范围内。然而,这一假设是错误的:
{-# LANGUAGE RankNTypes #-}
fun :: (forall a. [a] -> b) -> Bool
fun _ = undefined
arg :: c -> c
arg _ = undefined
r = fun arg -- type variable a would escape its scope
这是有意义的,因为在fun
b
中必须在a
之前选择,因此必须固定或独立于a
。在统一过程中,参数类型c->c
将强制b
使用[a0]
进行实例化
因此,类型级别的作用域可能非常类似于术语级别的函数,其中结果值显然不属于函数的作用域。换句话说,如果b
不是函数类型的密码域,则类型检查器将通过。不幸的是,我无法找到支持我推理的注释
一种限制性更强的方法是,在统一过程中,不允许使用同一注释的任何灵活类型实例化刚性类型变量。这两种方法对我来说似乎都是合理的,但这在Haskell中究竟是如何工作的呢?非量化类型变量在顶层被隐式量化。你的类型相当于
fun :: forall b . (forall a. [a] -> b) -> Bool
arg :: forall c . c -> c
因此,b
的范围是整个类型
您可以将所有'ed变量的每个看作函数接收的一种隐式附加类型参数。我想你明白,在一个签名中
foo :: Int -> (String -> Bool) -> Char
Int
值由foo
的调用者选择,而String
值由foo
在调用作为第二个参数传递的函数时(和如果)选择
本着同样的精神,
fun :: forall b. (forall a. [a] -> b) -> Bool
表示b
由fun
的调用者选择,而a
由fun
本身选择
调用方确实可以使用TypeAnnotations
显式地传递b
类型并写入
fun @Int :: (forall a. [a] -> Int) -> Bool
之后,将为所有a生成一个类型的参数。[a] ->Int
必须是apssed,并且length
适合这种多态类型
fun @Int length :: Bool
因为这是fun
的调用站点,所以我们看不到“type参数”a
在哪里传递。这确实只能在fun
的定义中找到
事实证明,实际上不可能定义一个fun
,该类型对其参数进行有意义的调用(length
,如上)。如果我们有这样一个稍微不同的签名:
fun :: forall b. Eq b => (forall a. [a] -> b) -> Bool
fun f = f @Int [1,2,3] /= f @Bool [True, False]
这里我们可以看到,
f
(通过调用绑定到length
)在两种不同的类型上被调用了两次。这将生成两个类型为b
的值,然后可以对其进行比较,以生成最终的Bool
非量化类型变量在顶级隐式量化。你的类型相当于
fun :: forall b . (forall a. [a] -> b) -> Bool
arg :: forall c . c -> c
因此,b
的范围是整个类型
您可以将所有'ed变量的每个看作函数接收的一种隐式附加类型参数。我想你明白,在一个签名中
foo :: Int -> (String -> Bool) -> Char
Int
值由foo
的调用者选择,而String
值由foo
在调用作为第二个参数传递的函数时(和如果)选择
本着同样的精神,
fun :: forall b. (forall a. [a] -> b) -> Bool
表示b
由fun
的调用者选择,而a
由fun
本身选择
调用方确实可以使用TypeAnnotations
显式地传递b
类型并写入
fun @Int :: (forall a. [a] -> Int) -> Bool
之后,将为所有a生成一个类型的参数。[a] ->Int
必须是apssed,并且length
适合这种多态类型
fun @Int length :: Bool
因为这是fun
的调用站点,所以我们看不到“type参数”a
在哪里传递。这确实只能在fun
的定义中找到
事实证明,实际上不可能定义一个fun
,该类型对其参数进行有意义的调用(length
,如上)。如果我们有这样一个稍微不同的签名:
fun :: forall b. Eq b => (forall a. [a] -> b) -> Bool
fun f = f @Int [1,2,3] /= f @Bool [True, False]
这里我们可以看到,
f
(通过调用绑定到length
)在两种不同的类型上被调用了两次。这将生成两个类型为b
的值,然后可以对其进行比较,以生成最终的Bool
对于没有显式forall
的任何变量,隐式的值将插入最近的:
,因此您的示例相当于fun::forall b。(forall a.[a]->b)->Bool
我发现将forall
视为lambda的类型级别等价物非常有用。与您的示例类似的术语级示例看起来像fun=\b->(\a->[a]`foo`b)`bar`True
@FyodorSoikin,这听起来有点不对劲。类型lambda是支持它们的语言中的术语级别的东西。Haskell的forall
只是一种在类型签名中显式显示类型参数的方法<代码>全部a。[a] ->b是一个术语的类型,其第一个参数是类型,a
,第二个参数是类型a
@DFEU的值,因为它们不仅仅是“明确的方式”,它们对于指定范围很重要。在rank-N类型之前,只有一个可能的作用域,因此Haskell对所有的都没有,但现在作用域可以不同了,这就突然需要了。看看我前段时间写的,它有一点关于这个主题。@FyodorSoikin,对,他们指定了范围。它们绑定名称,就像lambdas一样,但在其他方面却完全不同。对于没有显式forall
的任何变量,隐式变量正好插入最近的:
,因此您的示例相当于fun::forall b。(全部a)