Hutton在Haskell中进行编程说:
包含一个或多个类型变量的类型称为多态。
哪个是多态类型:一个类型或一组类型?
多态类型是用具体类型替换其类型变量的类型吗?
是否将具有不同具体类型的具体类型替换为其类型变量的多态类型视为相同或不同类型?
答案 0 :(得分:13)
多态类型是用具体类型替换其类型变量的类型吗?
是的,是的。但是,您需要小心。考虑:
id :: a -> a
那是多态的。您可以替换a := Int
以获得Int -> Int
,和a := Float -> Float
并获得(Float -> Float) -> Float -> Float
。但是,您不能说a := Maybe
并得到id :: Maybe -> Maybe
。那只是没有道理。相反,我们必须要求只能用Int
和Maybe Float
之类的具体类型代替a
,而不能用Maybe
之类的抽象类型代替。这由同类系统处理。这对于您的问题不太重要,因此我仅作总结。 Int
和Float
和Maybe Float
都是具体类型(即它们具有值),因此我们说它们具有类型Type
(类型的类型通常是称为同类)。 Maybe
是一个将具体类型作为参数并返回新的具体类型的函数,因此我们说Maybe :: Type -> Type
。在类型a -> a
中,我们说类型变量a
必须具有类型Type
,因此现在允许替换a := Int
,a := String
等,而a := Maybe
之类的东西不是。
是用不同的具体类型替换其类型变量的多态类型被认为是相同还是不同的类型?
不。返回a -> a
:a := Int
给出Int -> Int
,但是a := Float
给出Float -> Float
。不一样。
哪个是多态类型:一个类型或一组类型?
现在这是一个已加载的问题。您可以跳到最后的TL; DR,但是“什么是多态类型”这个问题在Haskell中实际上确实令人困惑,所以这是一堵文本墙。
有两种查看方式。 Haskell从一个开始,然后转移到另一个,现在我们有大量的旧文献都引用旧方法,因此现代系统的语法试图保持兼容性。有点混乱。考虑
id x = x
id
是什么类型?一种观点是id :: Int -> Int
以及id :: Float -> Float
和id :: (Int -> Int) -> Int -> Int
都是无限的。可以将这种无限的类型家族与一个多态类型id :: a -> a
相加。这种观点为您提供了Hindley-Milner type system。这不是现代GHC Haskell的工作原理,但该系统正是Haskell创建时所基于的。
在Hindley-Milner中,多态类型和单态类型之间有一条硬线,这两个组的并集通常为您提供“类型”。很难说,在HM中,多态类型(在HM行话中,“多型”)是类型。您不能将多型作为参数,也不能从函数中返回它们,也不能将它们放在列表中。相反,多型只是单型的模板。如果斜视,在HM中,可以将多态类型视为适合该模式的一组单型。
现代Haskell基于System F(带有扩展名)构建。在系统F中,
id = \x -> x -- rewriting the example
不是完整的定义。因此,我们甚至都不能考虑给它一个类型。每个lambda绑定变量都需要类型注释,但是x
没有注释。更糟糕的是,我们甚至无法做出决定:\(x :: Int) -> x
和\(x :: Float) -> x
一样好。在系统F中,我们要做的就是编写
id = /\(a :: Type) -> \(x :: a) -> x
使用/\
表示Λ
(大写lambda)就像使用\
表示λ
一样。
id
是一个带有两个个参数的函数。第一个参数是Type
,名为a
。第二个参数是a
。结果也是a
。类型签名是:
id :: forall (a :: Type). a -> a
forall
基本上是一种新型的功能箭头。请注意,它为a
提供了活页夹。在HM中,当我们说id :: a -> a
时,我们并没有真正定义a
是。这是一个新鲜的全局变量。按照惯例,最重要的是,该变量不会在其他任何地方使用(否则,Gen
概化规则将不适用,并且一切都会崩溃)。如果我写过inject :: a -> Maybe a
之后,a
的文本出现将引用一个新的全局实体,与id
中的实体不同。在系统F中,a
中的forall a. a -> a
实际上具有范围。它是仅在forall
下使用的“局部变量”。 a
中的inject :: forall a. a -> Maybe a
可以或可以不是“相同” a
;没关系,因为我们有实际的范围规则,可以防止一切崩溃。
因为系统F对于类型变量具有卫生的范围界定规则,所以多态类型可以执行其他类型可以做的所有事情。您可以将它们作为参数
runCont :: forall (a :: Type). (forall (r :: Type). (a -> r) -> r) -> a
runCons a f = f a (id a) -- omitting type signatures; you can fill them in
您将它们放入数据构造器中
newtype Yoneda f a = Yoneda (forall b. (a -> b) -> f b)
您可以将它们放置在多态容器中:
type Bool = forall a. a -> a -> a
true, false :: Bool
true a t f = t
false a t f = f
thueMorse :: [Bool]
thueMorse = false : true : true : false : _etc
与HM有一个重要区别。在HM中,如果某物具有多态类型,它也同时具有 ,单态类型的 infinity 。在系统F中,事物只能具有 one 类型。 id = /\a -> \(x :: a) -> x
的类型为forall a. a -> a
,而不是Int -> Int
或Float -> Float
。为了从Int -> Int
中提取id
,您必须给它一个参数:id Int :: Int -> Int
和id Float :: Float -> Float
。
但是,Haskell不是系统F。系统F更接近GHC所谓的Core,它是GHC将Haskell编译成的内部语言,基本上是Haskell,没有任何语法糖。 Haskell 是在System F核心之上的Hindley-Milner风味单板。在Haskell中,名义上多态类型是一种类型。它们的行为不像一组类型。但是,多态类型仍然是第二类。 Haskell不允许您在没有forall
的情况下实际键入-XExplicitForalls
。它通过在某些位置插入forall
来模仿Hindley-Milner的奇妙隐式全局变量创建。进行此操作的位置由-XScopedTypeVariables
更改。除非启用-XRankNTypes
,否则您不能采用多态参数或具有多态字段。您不能说类似[forall a. a -> a -> a]
之类的东西,也不能说id (forall a. a -> a -> a) :: (forall a. a -> a -> a) -> (forall a. a -> a -> a)
之类的东西,您必须定义例如newtype Bool = Bool { ifThenElse :: forall a. a -> a -> a }
将多态包装在单态下。除非启用-XTypeApplications
,然后才能编写id @Int :: Int -> Int
,否则您不能显式地提供类型参数。您不能编写类型为lambdas(/\
),句段;而是尽可能地隐式插入它们。如果定义id :: forall a. a -> a
,那么您甚至无法在Haskell中编写id
。它将始终隐式扩展到应用程序id @_
。
TL; DR :在Haskell中,多态类型是一种类型。它不会被视为一组类型,也不会被视为类型的规则/模式,等等。但是,由于历史原因,他们被视为二等公民。默认情况下,如果您稍作斜视,它们看起来就像只是类型集。可以通过适当的语言扩展来取消对它们的大多数限制,这时它们看起来更像是“正义类型”。剩下的一个大限制(不允许使用强制性实例化)相当基本,无法删除,但这很好,因为有一种解决方法。
答案 1 :(得分:1)
此处的“类型”一词有些细微差别。 值具有具体类型,不能为多态。另一方面,表达式具有通用类型,可以是多态的。如果您考虑使用值的类型,那么可以将多态类型宽松地定义为可能的具体类型的集合。 (至少是一阶多态类型!高阶多态性破坏了这种直觉。)但是,这并不总是一种特别有用的思维方式,也不是一个足够的定义。它没有捕获可以用这种方式描述的哪些类型集(以及相关的概念,例如参数性。)
这是一个很好的观察,在这两种相关但不同的方式中使用了相同的“类型”一词。
答案 2 :(得分:0)
编辑:以下答案是不回答问题。区别是术语上的一个细微错误:Maybe
和[]
这样的类型是高级的,而forall a. a -> a
和forall a. Maybe a
这样的类型是< em> polymorphic 。下面的答案与类型较高的类型有关,但是有人问了有关多态类型的问题。我仍然不回答这个问题,以防其他人受益,但是我现在意识到这并不是问题的答案。
我认为,一种 polymorphic 种类较多的类型更接近于 set 类型。例如,您可能会看到Maybe
是集合{Maybe Int
,Maybe Bool
,…}。
但是,严格来说,这有点误导。为了更详细地解决此问题,我们需要了解种类。与类型描述值的方式类似,我们说类型描述类型。这个想法是:
*
。示例包括Bool
,Char
,Int
和Maybe String
,它们的类型均为*
。这被表示为例如。 Bool :: *
。请注意,诸如Int -> String
之类的函数也具有类型*
,因为它们是具体类型,可以包含诸如show
之类的值!id :: a -> a
相同的方式,我们可以说Maybe :: * -> *
,因为Maybe
采用具体类型作为参数(例如Int
)并产生一个结果是具体类型(例如Maybe Int
)。像a -> a
这样的东西也具有种类* -> *
,因为它具有一个类型参数(a
)并产生具体结果(a -> a
)。您还可以得到更复杂的种类:例如,data Foo f x = FooConstr (f x x)
具有种类Foo :: (* -> * -> *) -> * -> *
。 (你知道为什么吗?)(如果上述解释没有意义,那么“学Haskell学”一书也有a great section on kinds。)
现在我们可以正确回答您的问题:
哪个是
多态的种类较多的类型:一个类型还是一组类型?
都不是:类型较高的多态类型是类型级功能,如实物箭头所示。例如,Maybe :: * -> *
是一种类型级别的函数,可转换例如Int
→Maybe Int
,Bool
→Maybe Bool
等
是一种
多态的种类更具体的类型,用其类型变量替换为类型的类型吗?
是,当您的多态更高类型的类型具有类型* -> *
(即,它具有一个类型参数,可以接受具体类型)时。当您将具体类型Conc :: *
应用于类型Poly :: * -> *
时,它只是函数应用程序,如上所述,结果为Poly Conc :: *
即具体类型。
是一种
多态较高型的类型,用不同的具体类型替换其类型变量被认为是相同还是不同的类型?
这个问题有点不合适,因为它与种类无关。答案肯定是否:Maybe Int
和Maybe Bool
这两种类型是不相同的。 Nothing
可能是这两种类型的成员,但只有前者包含值Just 4
,只有后者包含值Just False
。
另一方面,当结果类型为同构时, 可能具有两个不同的替换。 (<同形同构是两种类型不同,但在某种程度上等效的。例如,(a, b)
和(b, a)
是同构的,尽管类型相同。形式条件是当您可以编写两个反函数p
和q
时,两种类型p -> q
,q -> p
是同构的。)
一个例子是Const
:
data Const a b = Const { getConst :: a }
此类型只是忽略其第二个类型参数;结果,Const Int Char
和Const Int Bool
这两种类型是同构的。但是,它们类型不同:如果您输入类型为Const Int Char
的值,但是将其用作类型为Const Int Bool
的值,则将导致类型错误。这种功能非常有用,因为它意味着您可以使用a
'标记'类型Const a tag
,然后使用tag
作为类型级别的信息标记。>