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

时间:2014-06-24 03:43:52

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

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

Scala代码:

trait Box[T]

我定义了一个名为trait的{​​{1}},它是一个接受参数类型Box的类型构造函数。 (这句话是否正确?)

我还可以说:

  1. T是通用类型
  2. Box是一种更高级的
  3. 以上都不正确
  4. 当我与同事讨论代码时,我经常在“通用”和“高级”之间挣扎来表达它。

2 个答案:

答案 0 :(得分:1)

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

要回答您的特定问题,是的,Box是带有类型参数T的类型构造函数。您也可以说它是通用类型,尽管它不是更高种类的类型。下面是一个更广泛的答案。

这是Wikipedia对通用编程的定义:

泛型编程是一种计算机编程风格,其中,算法根据稍后要指定的类型编写,然后在需要时将其实例化以作为参数提供的特定类型。这种方法由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,因为编译器知道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,而无需传递Int任何类型参数。

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

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

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

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

enter image description here

答案 1 :(得分:0)

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

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

因此,T在英语中是通用的……我们不知道它将是什么,我们不在乎,因为我们不会直接在T上进行操作。

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