Абстрактные типы против параметров типа - PullRequest
21 голосов
/ 03 июля 2010

В каких ситуациях абстрактные типы следует отдавать предпочтение перед параметрами типа?

1 Ответ

18 голосов
/ 03 июля 2010

Чтобы добавить к моему предыдущему ответу о типе Abstract и параметрах , у вас также есть недавнее сообщение в блоге JESSE EICHAR (2010 г., 3 мая), в котором выделены некоторые ключевые различия:

trait C1[A] {
  def get : A
  def doit(a:A):A
}
trait C2 {
  type A
  def get : A
  def doit(a:A):A
}

В случае C2, параметр «похоронен» (как внутренний абстрактный тип).
(за исключением того, что, как говорит ретроним, он фактически не похоронен,см. ниже)

Принимая во внимание, что с универсальным типом, параметр явно упоминается, помогая другим выражениям узнать, какой тип они должны использовать


Итак (C1: параметр):

//compiles
def p(c:C1[Int]) = c.doit(c.get)

Он компилируется, но вы явно указываете тип 'A', который хотите использовать.

И (C2: Абстрактный тип):

// doesn't compile
def p2(c:C2) = c.doit(c.get)
<console>:6: error: illegal dependent method type
       def p2(c:C2) = c.doit(c.get)
              ^

Этоне компилируется, потому что 'A' никогда не упоминается в определении p2, поэтому doit не знает при типе компиляции, что он должен возвращать.


При использовании абстрактных типов и , желающих избежать «утечки типа» в интерфейс (т.е. желающих показать, что на самом деле является «A»), вы можете указатьочень общий тип в качестве возврата для p2:

// compiles because the internals of C2 does not leak out
def p(c:C2):Unit = c.doit(c.get)

Или вы можете "исправить" этот тип непосредственно в функции doit:
def doit(a:A):Int вместо def doit(a:A):A, что означает:
def p2(c:C2) = c.doit(c.get) скомпилируется (даже если p2 не упоминает тип возвращаемого значения)


Наконец (комментарий retronym ) вы можете указать A либо явно,уточнение абстрактного параметра C2:

scala> def p2(c:C2 { type A = Int }): Int = c.doit(c.get)
p2: (c: C2{type A = Int})Int

Или добавление параметра типа (и уточнение абстрактного типа C2 вместе с ним!)

scala> def p2[X](c:C2 { type A = X }): X = c.doit(c.get)
p2: [X](c: C2{type A = X})X

Так что абстрактные рекомендуется:

  • Если вы хотите скрыть точное определение члена типа от клиентского кода , используйте абстрактный тип, как в C2 (но будьте осторожны с определением функции с помощью C2)
  • Если вы хотите переопределить тип ковариантно в подклассах C2, мыАбстрактный тип (с абстракцией ограниченного типа)
  • Если вы хотите смешать определения этих C2 типов с помощью признаков , используйте абстрактный тип (у вас не будет 'A'иметь дело при смешивании C2 с вашим классом: вы смешиваете только C2)

В остальном, где требуется простой экземпляр типа , используйте Параметры.
(если вы знаете, что расширение не потребуется, но вам все равно придется обрабатывать несколько типов: для этого нужны типы параметров)


retronym добавляет:

Основными отличиями являются

  • Дисперсия : C2 может быть инвариантна только в A,
  • способ, которым члены типа могут быть выборочно переопределены в подтипе (тогда как параметры типа должны быть повторно объявлены и переданы супертипу)

(как , иллюстрирующее здесь :

trait T1 {
  type t
  val v: t
}
trait T2 extends T1 {
  type t <: SomeType1
}
trait T3 extends T2 {
  type t <: SomeType2  // where SomeType2 <: SomeType1
}
class C extends T3 {
  type t = Concrete    // where Concrete <: SomeType2
  val v = new Concrete(...)
}

)

...