[英]Why can't the type of id be specialised to (forall a. a -> a) -> (forall b. b -> b)?
[英]Why is forall a. a not considered a subtype of Int while I can use an expression of type forall a. a anywhere one of type Int is expected?
考虑以下一对函数定义,它们传递类型检查器:
a :: forall a. a
a = undefined
b :: Int
b = a
即表达类型forall a. a
forall a. a
可以在期望Int
类型之一的地方使用。 这在我看来很像子类型,但据称Haskell的类型系统缺少子类型。 这些形式的可替代性有何不同?
这个问题不是特定于forall a. a
forall a. a
。 其他例子包括:
id :: forall a. a -> a
id x = x
idInt :: Int -> Int
idInt = id
在类型化的lambda calculi中,我们有类型关系,通常表示为:
或者在Haskell中表示为::
。 通常,关系是“多对多”,因此类型可以包含多个值,并且值可以有多种类型。
特别是在多态类型系统中,值可以具有多种类型。 例如
map :: (a -> b) -> [a] -> [b]
但是也
map :: (Int -> Int) -> [Int] -> [Int].
在这种类型系统中,(有时)可以定义关于类型的关系,其含义是“比通用类型更多”, 类型顺序 。 如果t ⊑ s
则t
比s
更通用,这意味着如果M : t
也是M : s
,并且这种类型系统的输入规则允许准确地推断出。 或者我们说s
是t
。 所以在这个意义上, 类型有一个子类型关系。
但是,当我们谈论面向对象语言中的子类型时,我们通常意味着名义上的子类型 ,也就是说,我们声明哪些类型是什么的子类型,就像我们定义类继承时一样。 在Haskell中,它是类型的属性,独立于任何声明。 例如,任何类型都是forall a . a
forall a . a
。
对于允许类型推断并且是大多数函数式语言的基础的Hindley-Milner类型系统 ,存在主体类型的概念:如果表达式M
具有(任何)类型,那么它也具有其主要类型,并且主要类型是所有可能类型的M
的最一般类型。 关键特征是HM类型推断算法总是找到最通用的类型。 因此,最常见的推断主体类型可以专用于任何有效类型的M
有了这样的问题,我会退一步说,从根本上说,构成Haskell设计基础的数学理论是没有子类型概念的System F变体。
是的,可以查看Haskell的表面语法,并注意到有些情况就像你提出的那样,某些类型T
的表达式可以在任何需要T'
上下文中使用。 但这并不会出现,因为Haskell旨在支持子类型。 相反,它出现的事实是,Haskell被设计为比系统F的忠实渲染更加用户友好。
在这种情况下,它与以下事实有关:类型级量词通常不是在Haskell代码中显式编写的,类型级lambda和应用程序永远不会 。 如果你看一下类型forall a. a
forall a. a
从系统F角度,可替代性进入Int
语境消失。 a :: forall a. a
a :: forall a. a
是类型级别函数,不能在期望Int
的上下文中使用 - 您需要首先将它应用于Int
以获取a Int :: Int
,然后您可以在Int
上下文中实际使用它。 Haskell的语法以用户友好的名义隐藏,但它存在于基础理论中。
简而言之,虽然您可以通过将可以将哪些表达式类型替换为哪种上下文类型并且证明存在某种加密子类型关系来分析Haskell,但它只是没有成效,因为它会产生与设计当前游泳相关的分析。 它不是技术问题,而是意图和其他人为因素。
你是正确的类型forall a. a
类型的值forall a. a
forall a. a
可以在期望Int
任何地方使用,这意味着两种类型之间的子类型关系。 上面的其他答案试图说服你,这种“多态多于”的关系不是子类型。 然而,虽然它与典型的面向对象语言中的子类型形式肯定不同,但这并不意味着“更多 - 多态”的关系不能被视为(不同的)子类型形式。 实际上,多态类型系统的一些形式化在它们的子类型关系中精确地建模了这种关系。 例如,在Odersky和Läufer的论文“将类型注释投入工作”中的类型系统就是这种情况。
通过:: a
我们的意思是“任何类型”,但不是子类型。 a
可以是Int
,或Bool
,或IO (Maybe Ordering)
,但没有特别的。 a
不是一个完全类型,而是一个类型变量。
假设我们有这样的函数:
id x = x
编译器理解我们的参数x
没有特定的类型。 我们可以使用x
任何类型,只要它等同于id的任何类型。 所以,我们写签名如下:
-- /- Any type in...
-- | /- ...same type out.
-- V V
id :: a -> a
请记住,类型以Haskell中的大写字母开头。 这不是一个类型:它是一个类型变量!
我们使用多态,因为它更容易实现。 例如,合成是一个有用的想法:
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
(>>>) f g a = g (f a)
所以我们可以这样写:
plusOneTimesFive :: Int -> Int
plusOneTimesFive = (+1) >>> (* 5)
reverseHead :: [Bool] -> Bool
reverseHead = reverse >>> head
但是,如果我们必须像这样编写每种类型:
(>>>) :: (Bool -> Int) -> (Int -> String) -> (Bool -> String)
(>>>) f g a = g (f a)
(>>>') :: (Ordering -> Double) -> (Double -> IO ()) -> (Ordering -> IO ())
(>>>') f g a = g (f a)
(>>>'') :: (Int -> Int) -> (Int -> Bool) -> (Int -> Bool)
(>>>'') f g a = g (f a)
-- ...and so on.
那只是愚蠢的。
所以编译器使用类型统一来推断类型,如下所示:
假设我把它输入GHCi。 为简单起见,我们在Int
说6
。
id 6
编译器认为:“ id :: a -> a
,它正在传递一个Int
,所以a = Int
,所以id 6 :: Int
。
这不是子类型。 可以使用类型类捕获子类型,但这是基本的多态性。
它不是子类型,它是类型统一!
a :: forall a. a
a = undefined
b :: Int
b = a
在b = a
,我们将b
和a
约束为相同的类型,因此编译器会检查是否可能。 a
有类型forall a. a
forall a. a
,根据定义,它与每个类型统一,因此编译器会提出我们的约束。
类型统一还允许我们执行以下操作:
f :: (a -> Int) -> a
g :: (String -> b) -> b
h :: String -> Int
h = f g
通过统一, f :: (a -> Int) -> a
表示g
必须有类型a -> Int
,这意味着a -> Int
必须与(String -> b) -> b
统一,所以b
必须b
必须是Int
,这给g
的具体类型(String -> Int) -> Int
,这意味着a
是String -> Int
。
a -> Int
和(String -> b) -> b
都不是另一个的子类型,但它们可以统一为(String -> Int) -> Int
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.