Есть ли тип "SELF" в scala, который представляет текущий тип?
Я изучаю Scala, и есть что-то, что я не могу узнать о языке:
Некоторое время назад мне очень нравилось программировать в Lisaac, а в Lisaac я мог написать класс PERSON
с слотом list:ARRAY[SELF]
, который был эквивалентен list:ARRAY[PERSON]
, так как SELF
- это тип объект, в котором находится этот слот.
Но, используя SELF
, если я напишу второй класс STUDENT
, который наследует от PERSON
, тогда STUDENT
наследует изменение слота SELF
для STUDENT
, поэтому STUDENT
будет иметь список STUDENT
вместо PERSON
.
Можно ли это сделать в Scala? Я ничего не могу узнать об этом.
Спасибо!
Ответы
Ответ 1
Я не уверен, действительно ли это будет полезно для вас, но самое близкое, о чем я могу думать, - this.type
. Например:
scala> class A { val l: List[this.type] = Nil }
defined class A
scala> new A().l
res3: List[A] = List()
scala> class B extends A
defined class B
scala> new B().l
res4: List[B] = List()
Ответ 2
Для этого существует идиома, и она широко используется в рамках коллекций (во всех классах типа Like, например TraverseableLike).
Вам необходимо добавить self-type как параметр типа (как это возможно в С++ с CRTP) суперкласса:
trait Person[+Self] {
this: Self => //Declare that any concrete subclass must implement Self; therefore, this can be used with type Self.
//val list: Array[Self] //Not sure that this will work so easily, for the same reason new T[] does not work in Java.
val list = Seq[Self]() //No problem here; Array is really special.
}
После определения этого класса мы можем попытаться определить подклассы в интерпретаторе:
scala> class Student extends Person[Student]
defined class Student
scala> (new Student).list
res0: Seq[Student] = List() //Note the result type
scala> class Student2 extends Person[Student] //Note the mistake
<console>:9: error: illegal inheritance;
self-type Student2 does not conform to Person[Student] selftype Person[Student] with Student
class Student2 extends Person[Student]
Ошибка, которая не предотвращается, имеет такое определение, где Self не переопределяется:
scala> class SpecStudent extends Student
defined class SpecStudent
Благодаря +
перед Self
, что делает его ковариантным параметром типа (я не объясняю, что это такое), это, по крайней мере, возможно:
scala> class SpecStudentCorrect extends Student with Person[SpecStudentCorrect]
scala> (new SpecStudentCorrect).list
(new SpecStudentCorrect).list
res1: Seq[SpecStudentCorrect] = List()
Ответ 3
Ключевое слово this
в Scala более или менее эквивалентно.
При разработке расширяемого программного обеспечения иногда удобно явно объявить тип значения this
:
Явно напечатанные сообщения о себе в Scala
http://www.scala-lang.org/node/124
Ответ 4
Одиночные типы и ETSR не решают проблему. Я сам искал одну и ту же функцию в Scala, но, по-видимому, ей не хватает так называемых аннотаций самонастройки.
Существуют обстоятельства, при которых такие аннотации типа самостоятельного типа могут быть очень полезными. Рассмотрим пример (адаптирован из Пример вопроса о параметрах типового типа):
// we want a container that can store elements
trait Container[E <: Element[E]] {
def elements: Seq[E]
def add(elem: E): Unit
}
// we want elements be aware of their enclosing container
trait Element[E <: Element[E]] {
def container: Container[E]
}
Скажем, вы положили это в библиотеку. Потребитель библиотеки должен сделать следующее:
object PersonContainer extends Container[Person] {
// actual implementation is not important
def elements = Nil
def add(p: Person) = {}
}
class Person extends Element[Person] { // {1}
def container = PersonContainer
}
Все в порядке, и все работает так, как ожидалось. Единственное, что касается того, что потребитель библиотеки должен использовать параметр self-bound type (# 1 в коде). Но это не все. Предположим теперь, что у вас есть какой-то шаблон ActiveRecord, и вы хотите добавить метод save
в Element
, который просто делегирует ему контейнер add
. Удивительно, но это не так просто:
trait Element[E <: Element[E]] {
def container: Container[E]
def save() = container.add(this) // won't compile
}
found : Element[E]
required: E
Интуитивно, у нас есть несколько вариантов:
- make
add
метод принимает Element[E]
вместо E
;
- отбрасывает
this
в Element[E]
.
Ни один из этих параметров не является удовлетворительным, только из-за того, что E
не совпадает с Element[E]
(реализации не вынуждены использовать параметры с самозашиваемым типом). Единственное, что я вижу для решения этой проблемы, - это концепцию самонаведения в Scala (предположим, что мы имеем ее на нашем любимом языке):
trait Container[E <: Element] {
def elements: Seq[E]
def add(elem: E): Unit
}
trait Element { // the type parameter would be redundant ...
def save() = container.add(this) // ... and this would be possible, too, ...
def container: Container[this] // ... if only we could do this
}
Если компилятор может рассматривать this
(или, возможно, другое ключевое слово), когда он используется в квадратных скобках, в качестве типа фактической реализации (т.е. того же типа, что и результат obj.getClass
), тогда проблемы исчезнет.
P.S. Может кто-нибудь подумать о включении этого материала в список желаний Scala? К сожалению, я не знаю, как сложно реализовать такую логику, поскольку могут возникнуть проблемы с пресловутой стиранием JVM.
P.P.S. Или, может быть, есть еще одна Scala -уровень, о которой я не знаю?