这可能已经太晚回答了,你现在可能已经知道了区别,但我想提供一个不同的观点来回答,因为我不确定Greg所说的是否正确。泛型比高阶类型更通用。许多语言,如Java和C#都有泛型,但很少有高阶类型。
回答您的具体问题,是的,Box是具有类型参数T的类型构造函数。您还可以说它是一种泛型类型,尽管它不是高阶类型。下面是更广泛的答案。
这是泛型编程的维基百科定义:
泛型编程是一种计算机编程风格,其中算法以稍后指定的类型为基础编写,当需要针对特定类型提供参数时进行实例化。这种方法由ML在1973年开创,
1允许编写仅在它们操作的类型集合上不同的常见函数或类型,从而减少重复。
假设你像这样定义了一个“Box”。它包含某种类型的元素,并具有一些特殊方法。它还定义了一个“map”函数,类似于“Iterable”和“Option”,因此您可以将包含整数的盒子转换为包含字符串的盒子,而不会失去所有那些“Box”具有的特殊方法。
case class Box(elem: Any) {
..some special methods
def map(f: Any => Any): Box = Box(f(elem))
}
val boxedNum: Box = Box(1)
val extractedNum: Int = boxedString.elem.asInstanceOf[Int]
val boxedString: Box = boxedNum.map(_.toString)
val extractedString: String = boxedString.elem.asInstanceOf[String]
如果
Box
的定义如下,由于需要频繁调用
asInstanceOf
,您的代码将变得非常丑陋,但更重要的是,它不是类型安全的,因为一切都是Any。
这就是泛型有用的地方。假设我们改为以下方式定义
Box
:
case class Box[A](elem: A) {
def map[B](f: A => B): Box[B] = Box(f(elem))
}
然后,我们可以使用
map
函数来做各种事情,比如改变
Box
内的对象,同时确保它仍在
Box
内。这里不需要使用
asInstanceOf
,因为编译器知道你的
Box
的类型和它们所持有的内容(即使类型注解和类型参数也不是必需的)。
val boxedNum: Box[Int] = Box(1)
val extractedNum: Int = boxedNum.elem
val boxedString: Box[String] = boxedNum.map[String](_.toString)
val extractedString: String = boxedString.elem
泛型基本上允许您对不同类型进行抽象,使您可以将Box[Int]
和Box[String]
用作不同的类型,即使您只需要创建一个Box
类。
然而,假设您无法控制这个
Box
类,它只是定义为:
case class Box[A](elem: A) {
}
假设你正在使用的API还定义了自己的
Option
和
List
类(都接受一个表示元素类型的单个类型参数)。现在你想要能够映射所有这些类型,但由于你无法自行修改它们,你将不得不定义一个隐式类来创建它们的扩展方法。让我们添加一个隐式类
Mappable
作为扩展方法和一个类型类
Mapper
。
trait Mapper[C[_]] {
def map[A, B](context: C[A])(f: A => B): C[B]
}
implicit class Mappable[C[_], A](context: C[A])(implicit mapper: Mapper[C]) {
def map[B](f: A => B): C[B] = mapper.map(context)(f)
}
您可以像这样定义隐式映射器。
implicit object BoxMapper extends Mapper[Box] {
def map[B](box: Box[A])(f: A => B): Box[B] = Box(f(box.elem))
}
implicit object OptionMapper extends Mapper[Option] {
def map[B](opt: Option[A])(f: A => B): Option[B] = ???
}
implicit object ListMapper extends Mapper[List] {
def map[B](list: List[A])(f: A => B): List[B] = ???
}
并将其用作如果Box
,Option
,List
等始终具有map
方法。
在这里,Mappable
和Mapper
是高阶类型,而Box
,Option
和List
是一阶类型。它们都是通用类型和类型构造函数。Int
和String
是适当的类型。以下是它们的kinds(种类对于类型就像类型对于值一样)。
//To check the kind of a type, you can use :kind in the REPL
Kind of Int and String: *
Kind of Box, Option, and List: * -> *
Kind of Mappable and Mapper: (* -> *) -> *
类型构造器有点类似于函数(有时被称为值构造器)。一个适当的类型(种类
*
)就像一个简单的值,是可以用作返回类型、变量类型等的具体类型。您可以直接说
val x: Int
而不传递任何类型参数。
一阶类型(种类
* -> *
)就像一个看起来像
Any => Any
的函数。它不是获取值并给出值,而是获取类型并给出另一个类型。您不能直接使用一阶类型(
val x: List
不起作用),必须给它们提供类型参数(
val x: List[Int]
可以工作)。这就是泛型所做的——它允许您抽象出类型并创建新类型(JVM在运行时只会擦除该信息,但像C++这样的语言会生成新的类和函数)。
Mapper
中的类型参数
C
也属于这种类型。下划线类型参数(您也可以使用其他名称,如
x
)让编译器知道
C
是种类
* -> *
。
高阶类型(higher-kinded type)类似于高阶函数,它将另一个类型构造器作为参数。你不能使用上面的
Mapper[Int]
,因为
C
应该是
* -> *
种类的(这样你就可以执行
C[A]
和
C[B]
),而
Int
仅仅是
*
。只有在Scala和Haskell等具有高阶类型的语言中才能创建像上面的
Mapper
和其他超出Java等类型系统有限的语言的类型。
这个答案(以及其他一些)对类似问题也有帮助。
编辑:我从同样的答案中偷了这张非常有用的图片:
Box
不是一个类型,但Box[_]
是。 - Chris Martin