为什么是forall a。一个不被认为是Int的子类型,而我可以使用类型forall a的表达式。预计会有任何一种类型的Int?

时间:2015-09-23 18:41:28

标签: haskell parametric-polymorphism subtyping type-theory

考虑以下一对函数定义,它们传递类型检查器:

a :: forall a. a
a = undefined

b :: Int
b = a

即。类型forall a. a的表达式可用于需要Int类型的表达式。这在我看来很像子类型,但据称Haskell的类型系统缺少子类型。这些形式的可替代性有何不同?

此问题并非针对forall a. a。其他例子包括:

id :: forall a. a -> a
id x = x

idInt :: Int -> Int
idInt = id

5 个答案:

答案 0 :(得分:14)

在类型化的lambda calculi中,我们有类型关系,通常表示为:,或者在Haskell中表示为::。一般来说,关系是"多对多",因此一个类型可以包含多个值,并且一个值可以有多种类型。

特别是在多态类型系统中,值可以有多种类型。例如

map :: (a -> b) -> [a] -> [b]

但也

map :: (Int -> Int) -> [Int] -> [Int].

在这种类型的系统中,它(有时)可以定义具有含义的类型的关系"更通用的类型而不是",type order。如果t ⊑ s那么ts更通用,意味着如果M : t那么M : s,并且这种类型系统的输入规则允许推断确切地说。或者我们说st的特化。所以在这个意义上,类型有subtyping关系。

然而,当我们谈论面向对象语言中的子类型时,我们通常意味着nominal subtyping,也就是说,我们声明哪些类型是什么的子类型,就像我们定义类继承时一样。在Haskell中,它是类型的属性,独立于任何声明。例如,任何类型都是forall a . a的专业化。

对于允许类型推断的Hindley-Milner type system,它是大多数函数式语言的基础,有principal type的概念:如果表达式M具有(任何)类型,那么它也有它的主要类型,主体类型是所有可能类型M中最常见的类型。关键特征是HM类型推断算法总是找到最通用的类​​型。因此,最常见的推断主体类型可以专用于任何有效类型的M

答案 1 :(得分:11)

有了这样的问题,我会退一步说,从根本上说,构成Haskell设计基础的数学理论是System F变体,它们没有子类型的概念。

是的,可以查看Haskell的表面语法,并注意有些情况就像你提出的那样,某些类型T的表达式可以在T'所在的任何上下文中使用预期。但这并不会出现,因为Haskell旨在支持子类型。相反,它出现的事实是,Haskell被设计为比系统F的忠实渲染更加用户友好。

在这种情况下,它与以下事实有关:类型级量词通常不是在Haskell代码中显式编写的,类型级lambda和应用程序从不。如果从系统F角度查看类型forall a. a,则Int上下文中的可替代性就会消失。 a :: forall a. a是一个类型级别的函数,无法在需要Int的上下文中使用 - 您需要先将其应用到Int以获取a Int :: Int,这就是你的意思实际上可以在Int上下文中使用。 Haskell的语法以用户友好的名义隐藏,但它存在于基础理论中。

简而言之,虽然您可以通过将可以将哪些表达式类型替换为哪种上下文类型并且证明存在某种加密子类型关系来分析Haskell,但它只是没有成效,因为它会产生与当前游戏相关的分析的设计。并不是技术问题,而是意图和其他人为因素。

答案 2 :(得分:5)

你是正确的,类型forall a. a的类型可以在预期Int的任何地方使用,这意味着两种类型之间的子类型关系。上面的其他答案试图说服你,这种“多态多于”的关系不是子类型。然而,虽然它与典型的面向对象语言中的子类型形式肯定不同,但这并不意味着“更多 - 多态”的关系不能被视为(不同的)子类型形式。实际上,多态类型系统的一些形式化在它们的子类型关系中精确地建模了这种关系。例如,在Odersky和Läufer的论文"Putting type annotations to work"中的类型系统就是这种情况。

答案 3 :(得分:3)

:: a我们的意思是"任何类型",但不是子类型。 a 可以 IntBoolIO (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。为简单起见,我们在6中说Int

id 6

编译器认为:" id :: a -> a,它传递的是Int,所以a = Int,所以id 6 :: Int

这是子类型。可以使用类型类捕获子类型,但这是基本的多态性。

答案 4 :(得分:2)

它不是子类型,它是type unification!

a :: forall a. a
a = undefined

b :: Int
b = a

b = a中,我们将ba限制为相同类型,因此编译器会检查这是否可能。 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,这意味着aString -> Int

a -> Int(String -> b) -> b都不是另一个的子类型,但它们可以统一为(String -> Int) -> Int