F-ограниченные типы и методы с параметрами типов на сайтах аргументов и возврата
У меня есть F-ограниченный тип, и моя цель - создать метод с параметризацией типа, чтобы иметь возможность использовать его повторно. Вот пример кода:
trait FType {
type ThisType <: FType
def deepCopy(): ThisType
}
class ConcreteType extends FType {
override type ThisType = ConcreteType
override def deepCopy(): ConcreteType = this
}
class ConcreteType2 extends FType {
override type ThisType = ConcreteType2
override def deepCopy(): ConcreteType2 = this
}
object FType {
def deepCopy[T <: FType](_type: T): T = {
_type.deepCopy()
}
/* def deepCopy2(c: ConcreteType2): ConcreteType2 = {
c.deepCopy()
}*/
def main(args: Array[String]): Unit = {
//deepCopy2(new ConcreteType2)
}
}
Тем не менее, код не компилируется. Компилятор выдает эту ошибку:
Error:(29, 19) type mismatch;
found : _type.ThisType
required: T
_type.deepCopy()
Я понимаю, что это имеет отношение к зависимым от пути типам, так как _type.ThisType
не тот же тип, что и T
,
Но как же тогда я могу воспользоваться F-ограниченными типами, если F-ограниченный тип не совсем совпадает с типом, использующим F-ограниченный тип? Если типы не совпадают, как это deepCopy2
компилирует?
Примечание: я знаю, что могу избежать использования параметров типа в deepCopy
используя метод перегрузки для каждого из конкретных типов.
2 ответа
Если типы не совсем одинаковы, как компилируется этот deepCopy2?
Это довольно просто. Это работает, потому что нет вовлеченного полиморфизма. Статически известно, что ConcreteType2
имеет deepCopy()
метод, который возвращает ConcreteType2
, Вы могли бы даже удалить type ThisType
из всей иерархии, и это все равно будет работать так же.
Но как же тогда я могу воспользоваться F-ограниченными типами, если F-ограниченный тип не совсем совпадает с типом, использующим F-ограниченный тип?
Вы должны сказать компилятору, что он такой же, как вы не указали достаточно. Давайте посмотрим на пример, который работает и является полиморфным:
def deepCopy[A <: FType { type ThisType = A }](_type: A): A = _type.deepCopy()
// ^ --important bit-- ^
Это определяет метод, который работает для любого A
то есть FType
а также это член типа ThisType
установлен в A
связывая их вместе. Это означает, что это будет работать для ваших определений ConcreteType
а также ConcreteType2
, Однако он НЕ будет компилироваться для классов, которые не определены должным образом, таких как этот:
class Bogus extends FType {
override type ThisType = ConcreteType2
override def deepCopy(): ConcreteType2 = new ConcreteType2
}
deepCopy(new Bogus)
В качестве альтернативы, давайте начнем с немного модифицированной версии вашего метода:
def deepCopyPD[A <: FType](_type: A): _type.ThisType = _type.deepCopy()
^path-dependent^
Это не накладывает никаких ограничений на ThisType
, но на самом деле компилятор сможет определить правильные версии для всех случаев:
val x: ConcreteType2 = deepCopyPD(new ConcreteType2)
val y: ConcreteType2 = deepCopyPD(new Bogus) // yep, this invocation is possible with such signature
Однако также возможно дополнительно добавить ограничения, используя свидетельство равенства типов в качестве неявного параметра:
def deepCopyPD2[A <: FType](_type: A)(implicit ev: _type.ThisType =:= A): A = _type.deepCopy()
Это, опять же, запрещает вызов с Bogus
В F-ограниченных типах у вас обычно ThisType
в качестве параметра типа вместо члена типа:
trait FType[ThisType <: FType] {
def deepCopy(): ThisType
}
class ConcreteType extends FType[ConcreteType] {
override def deepCopy(): ConcreteType = this
}
// in object FType
def deepCopy[T <: FType[T]](_type: T): T = {
_type.deepCopy()
}
Обратите внимание на разницу: в FType.deepCopy
компилятор знает, что тип возвращаемого значения _type.deepCopy()
является T
,
Вы можете сделать то же самое с членами типа:
def deepCopy[T <: FType { type ThisType <: T }](_type: T): T =
_type.deepCopy()