【问题标题】:What's the difference between "Generic type" and "Higher-kinded type"?“通用类型”和“高级类型”有什么区别?
【发布时间】:2014-08-14 04:55:31
【问题描述】:

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

Scala 代码:

trait Box[T]

我定义了一个名为Boxtrait,它是一个接受参数类型T的类型构造函数。 (这句话对吗?)

我也可以说:

  1. Box 是泛型类型
  2. Box 是高级类型
  3. 以上都不对

当我与同事讨论代码时,我经常在“通用”和“高级”两个词之间纠结。

【问题讨论】:

标签: scala generics type-systems higher-kinded-types


【解决方案1】:

现在回答可能为时已晚,而且您现在可能已经知道其中的区别,但我将回答只是为了提供另一种观点,因为我不太确定 Greg 所说的是否正确。泛型比更高种类的类型更通用。很多语言,例如 Java 和 C# 都有泛型,但很少有更高种类的类型。

要回答您的具体问题,是的,Box 是一个类型构造函数,其类型参数为T。您也可以说它是泛型类型,尽管它不是更高种类的类型。下面是一个更广泛的答案。

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

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

假设您这样定义Box。它拥有某种类型的元素,并有一些特殊的方法。它还定义了一个map 函数,类似于IterableOption,因此您可以将一个包含整数的盒子变成一个包含字符串的盒子,而不会丢失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,因为编译器知道你的Boxes 的类型以及它们包含的内容(甚至不需要类型注释和类型参数)。

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: (* -> *) -> *

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

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

高阶类型/高阶类型就像高阶函数——它接受另一个类型构造函数作为参数。你不能使用上面的Mapper[Int],因为C 应该类似于* -> *(这样你就可以使用C[A]C[B]),而Int 只是*。只有在 Scala 和 Haskell 等具有更高种类类型的语言中,您才能创建像上面的 Mapper 这样的类型,以及具有更有限类型系统的语言(如 Java)之外的其他东西。

这个answer(和其他人)对类似问题也可能有所帮助。

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

【讨论】:

    【解决方案2】:

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

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

    所以T 在英语意义上是通用的......我们不知道它会是什么,我们也不在乎,因为我们不会直接对T 进行操作。

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

    【讨论】:

      猜你喜欢
      • 2010-10-02
      • 2010-12-11
      • 2012-02-06
      • 1970-01-01
      • 2023-01-04
      • 1970-01-01
      • 1970-01-01
      • 2011-07-07
      • 2016-10-09
      相关资源
      最近更新 更多