В чем разница между родовым и высокородным типом?

Я обнаружил, что действительно не понимаю разницы между «родовым типом» и «высшим типом».

Код Scala:

trait Box[T]

Я определил trait с именем Box, который является конструктором типа, который принимает тип параметра T. (Правильно ли это предложение?)

Могу я также сказать:

  1. Box - это общий тип
  2. Box - высший тип
  3. Ничего из вышеперечисленного неверно

Когда я обсуждаю код с моими коллегами, я часто пытаюсь выразить его словами «общий» и «высший тип».


person Freewind    schedule 24.06.2014    source источник
comment
Box не тип, но Box[_].   -  person Chris Martin    schedule 24.06.2014
comment
stackoverflow.com/ questions / 6246719 / Взгляните на это   -  person jilen    schedule 24.06.2014
comment
Только что нашел через 1 год, я все еще не понимаю этот вопрос   -  person Freewind    schedule 02.08.2015


Ответы (2)


Вероятно, сейчас уже слишком поздно отвечать, и вы, вероятно, уже знаете разницу, но я собираюсь ответить, чтобы предложить альтернативную точку зрения, поскольку я не уверен, что то, что говорит Грег, правильно. Дженерики являются более общими, чем более высокородные типы. Многие языки, такие как 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, поскольку компилятор знает тип ваших 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, который вы используете, также определяет свои собственные классы 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] = ???
}
//and so on

и используйте его, как если бы Box, Option, List и т. д. всегда имели map методы.

Здесь Mappable и Mapper - это типы более высокого порядка, а Box, Option и List - типы первого порядка. Все они являются универсальными типами и конструкторами типов. 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: (* -> *) -> *

Конструкторы типов в некоторой степени аналогичны функциям (которые иногда называют конструкторами значений). Правильный тип (вид *) аналогичен простому значению. Это конкретный тип, который вы можете использовать для возвращаемых типов, таких как типы ваших переменных и т. Д. Вы можете просто прямо сказать 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.

Этот ответ (и другие) на аналогичный вопрос также может помочь.

Изменить: я украл это очень полезное изображение из того же ответа:

введите описание изображения здесь

person user    schedule 29.06.2020

Нет разницы между «высокородными типами» и «дженериками».

Box - это «структура» или «контекст», а T может быть любого типа.

Итак, T является общим в английском смысле ... мы не знаем, что это будет, и нам все равно, потому что мы не собираемся работать с T напрямую.

В C # они также называются Generics. Я подозреваю, что они выбрали этот язык из-за его простоты (чтобы не отпугивать людей).

person Greg    schedule 17.11.2018