В каких ситуациях должны быть предпочтительны абстрактные типы по параметрам типа?
Абстрактные типы по сравнению с параметрами типа
Ответ 1
Чтобы добавить к моему предыдущему ответу абстрактный тип и параметры, у вас также есть Недавнее сообщение в блоге 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
с вашим class: вы смешиваете только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(...)
}
)