"Generic type"和"Higher-kinded type"有什么区别?

Fre*_*ind 7 generics type-systems scala higher-kinded-types

我发现自己真的无法理解"通用类型"和"高级类型"之间的区别.

Scala代码:

trait Box[T]
Run Code Online (Sandbox Code Playgroud)

我定义了一个trait名称是Box,它是一个接受参数类型的类型构造函数T.(这句话是否正确?)

我还能说:

  1. Box 是一种通用类型
  2. Box 是一种更高级的类型
  3. 以上都不正确

当我与同事讨论代码时,我经常在"通用"和"高级"之间挣扎来表达它.

use*_*ser 11

现在回答可能已经太晚了,你现在可能知道其中的区别,但我回答只是为了提供替代的观点,因为我不太确定格雷格说的是对的。泛型比高级类型更通用。许多语言(例如 Java 和 C#)都具有泛型,但很少有语言具有更高级的类型。

要回答您的具体问题,是的,Box是带有类型参数的类型构造函数T。您也可以说它是泛型类型,尽管它不是更高级的类型。以下是更广泛的答案。

这是维基百科对泛型编程的定义:

通用编程是一种计算机编程风格,其中算法是根据稍后指定的类型编写的,然后在需要时实例化作为参数提供的特定类型。这种方法由 ML 于 1973 年首创,1允许编写通用函数或类型,这些函数或类型仅在使用时所操作的类型集上有所不同,从而减少重复。

假设您Box这样定义。它包含某种类型的元素,并且有一些特殊的方法。它还定义了一个map函数,例如Iterableand 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]
Run Code Online (Sandbox Code Playgroud)

如果Box像这样定义,你的代码会因为所有对 的调用而变得非常丑陋asInstanceOf,但更重要的是,它不是类型安全的,因为一切都是 Any。

这就是泛型可以发挥作用的地方。假设我们Box这样定义:

case class Box[A](elem: A) {
  def map[B](f: A => B): Box[B] = Box(f(elem))
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以将我们的map函数用于各种事情,例如更改内部的对象,Box同时仍然确保它位于Box. 在这里,不需要,asInstanceOf因为编译器知道 es 的类型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
Run Code Online (Sandbox Code Playgroud)

泛型基本上可以让您抽象不同的类型,让您可以使用Box[Int]Box[String]作为不同的类型,即使您只需要创建一个Box类。


但是,假设您无法控制此类Box,并且它仅定义为

case class Box[A](elem: A) {
  //some special methods, but no map function
}
Run Code Online (Sandbox Code Playgroud)

假设您使用的这个 API 还定义了它自己OptionList类(都接受表示元素类型的单个类型参数)。现在您希望能够映射所有这些类型,但由于您无法自己修改它们,因此您必须定义一个隐式类来为它们创建扩展方法。让我们Mappable为扩展方法添加一个隐式类和一个 typeclass 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)
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样定义隐式映射器

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
Run Code Online (Sandbox Code Playgroud)

并使用它,就好像BoxOptionList等一直都有map方法一样。

这里,MappableMapper是高阶类型,而BoxOptionList是一阶类型。它们都是泛型类型和类型构造函数。然而,Int和是正确的类型。String这是它们的种类(种类之于类型就像类型之于值)。

//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: (* -> *) -> *
Run Code Online (Sandbox Code Playgroud)

类型构造函数有点类似于函数(有时称为值构造函数)。正确的类型(kind *)类似于简单的值。它是一种具体类型,可用于返回类型、变量类型等。您可以直接说出val x: Int而不传递Int任何类型参数。

一阶类型 (kind * -> *) 就像一个看起来像 的函数Any => Any。它不是接受一个值并给你一个值,而是接受一个类型并给你另一种类型。val x: List如果不给它们类型参数(有效),则不能直接使用一阶类型(val x: List[Int]无效)。这就是泛型的作用 - 它允许您抽象类型并创建新类型(JVM 只是在运行时删除该信息,但像 C++ 这样的语言实际上会生成新的类和函数)。C中的类型参数Mapper也是这种类型。下划线类型参数(您也可以使用其他名称,例如x)让编译器知道它C属于 类型* -> *

高种类类型/高阶类型就像高阶函数 - 它采用另一个类型构造函数作为参数。您不能使用Mapper[Int]上面的 a ,因为C应该是一种类型* -> *(以便您可以执行C[A]and C[B]),而Intis 只是*。只有在像 Scala 和 Haskell 这样具有更高级类型的语言中,您才能创建像Mapper上面这样的类型,以及超出类型系统更有限的语言(如 Java)的其他东西。

关于类似问题的这个答案(和其他答案)也可能有帮助。

编辑:我从同一个答案中窃取了这张非常有用的图片:

在此输入图像描述


Gre*_*reg -8

“高等种类”和“泛型”之间没有区别。

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

英语意义上的通用也是如此T……我们不知道它会是什么,我们也不关心,因为我们不会T直接进行操作。

C# 也将它们称为泛型。我怀疑他们选择这种语言是因为它简单(不要吓跑人们)。