“泛型类型”和“高阶类型”有什么区别?

13
我发现自己真的不太理解"泛型类型"和"高阶类型"之间的区别。
Scala 代码:
trait Box[T]

我定义了一个名为Boxtrait,它是一个类型构造器,接受一个参数类型T。(这个句子正确吗?)
我还可以说:
  1. Box是一个泛型类型。
  2. Box是一个高阶类型。
  3. 以上都不正确。
当我和同事讨论代码时,我经常在单词“generic”和“higher-kinded”之间犹豫,以表达它。

2
Box 不是一个类型,但 Box[_] 是。 - Chris Martin
1
看一下这个:https://dev59.com/vm025IYBdhLWcg3wCxVN - jilen
2
刚发现已经过去了一年,我仍然不清楚这个问题。 - Freewind
2个回答

10
这可能已经太晚回答了,你现在可能已经知道了区别,但我想提供一个不同的观点来回答,因为我不确定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) {
  //some special methods, but no map function
}

假设你正在使用的API还定义了自己的OptionList类(都接受一个表示元素类型的单个类型参数)。现在你想要能够映射所有这些类型,但由于你无法自行修改它们,你将不得不定义一个隐式类来创建它们的扩展方法。让我们添加一个隐式类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] = ???
}
//and so on

并将其用作如果BoxOptionList等始终具有map方法。

在这里,MappableMapper是高阶类型,而BoxOptionList是一阶类型。它们都是通用类型和类型构造函数。IntString是适当的类型。以下是它们的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等类型系统有限的语言的类型。

这个答案(以及其他一些)对类似问题也有帮助。

编辑:我从同样的答案中偷了这张非常有用的图片:

enter image description here


-7

“Higher-Kinded Types”和“Generics”之间没有区别。

Box是一个“结构”或“上下文”,T可以是任何类型。

因此,T在英语意义上是泛型...我们不知道它将会是什么,也不关心,因为我们不会直接操作T

C#也称之为Generics。我猜他们选择这种语言是因为它的简单性(不会吓到人们)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接