[英]Why `f x = x x` and `g x = x x x x x` have the same type
我正在玩 Rank-N-type 并尝试输入xx
。 但是我发现这两个函数可以以相同的方式输入是违反直觉的。
f :: (forall a b. a -> b) -> c
f x = x x
g :: (forall a b. a -> b) -> c
g x = x x x x x
我还注意到fx = xx ... xx
(many x
s) 之类的东西仍然具有相同的类型。 谁能解释为什么会这样?
关键是x :: a -> b
是一个可以提供任何类型值的函数,无论给出什么参数。 这意味着x
可以应用于自身,结果可以再次应用于x
,依此类推。
至少,这就是它承诺的类型检查器可以做的事情。 类型检查器不关心是否存在任何这样的值,只关心类型是否对齐。 f
和g
实际上都不能被调用,因为不存在a -> b
类型a -> b
值(忽略 bottom 和unsafeCoerce
)。
这不应该比以下事实更令人惊讶
m :: (∀ a . a) -> (∀ a . a) -> (Int, Bool)
m p q = (p, q)
具有相同的类型
n :: (∀ a . a) -> (∀ a . a) -> (Int, Bool)
n p q = (q, p)
就像在您的示例中一样,这是有效的,因为通用量化参数可以以多种不同的方式使用,编译器在每种情况下都会选择适当的类型并强制x
充当具有该类型的角色。
这实际上是一个相当人为的情况,因为像∀ a . a
这样的类型∀ a . a
∀ a . a
或∀ ab . a->b
∀ ab . a->b
是无人居住的(模⊥),因此您实际上永远无法使用带有此类参数的 RankN 函数; 实际上,那时你甚至不会写它!
实用的 RankN 函数通常会在其参数中强加一些额外的结构或类型类约束,例如
foo :: (∀ a . [a] -> [a]) -> ...
或者
qua :: (∀ n . Num n -> Int -> n -> n) -> ...
每当我们使用具有多态类型的变量(例如您的x
)时,都可以观察到这种现象。 身份函数id
可能是最著名的例子。
id :: forall a . a -> a
在这里,所有这些表达式都进行类型检查,并且类型为Int -> Int
:
id :: Int -> Int
id id :: Int -> Int
id id id :: Int -> Int
id id id id :: Int -> Int
...
这怎么可能? 好吧,关键是每次我们写id
我们实际上是指“应该从上下文中推断出的某个未知类型a
上的身份函数”。 至关重要的是,每次使用id
都有自己的a
。
让我们写id @T
来表示类型T
上的特定标识函数。
写作
id :: Int -> Int
实际上是指
id @Int :: Int -> Int
这很简单。 相反,写
id id :: Int -> Int
实际上是指
id @(Int -> Int) (id @Int) :: Int -> Int
其中第一个id
现在指的是函数空间Int -> Int
! 而且当然,
id id id :: Int -> Int
方法
(id @((Int -> Int) -> (Int -> Int))) (id @(Int -> Int)) (id @Int) :: Int -> Int
等等。 我们没有意识到类型会变得如此混乱,因为 Haskell 会为我们推断这些类型。
在您的具体情况下,
g :: (forall a b. a -> b) -> c
g x = x x x x x
我们可以通过多种方式进行类型检查。 一种可能的方法是定义A ~ Int
, B ~ Bool
, T ~ (A -> B)
然后推断:
g x = x @T @(T -> T -> T -> c) (x @A @B) (x @A @B) (x @A @B) (x @A @B)
我建议花一些时间来了解所有类型的检查。 (此外,我们对A
和B
选择是完全任意的,我们可以在那里使用任何其他类型。我们甚至可以为每个x
使用不同的A
s 和B
s,只要第一个x
被适当地实例化!)
很明显,即使当xxx ...
是一个更长的序列时,这种推断也是可能的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.