简体   繁体   中英

Is there a “SELF” type in scala that represents the current type?

I'm learning Scala and there is a thing that I can't find out about the language:

Some time ago I was very comfortable programming in Lisaac, and in Lisaac I could write a class PERSON with a slot list:ARRAY[SELF] , which was equivalent to have list:ARRAY[PERSON] , since SELF is the type of the object where that slot is.

But by using SELF , if I write a second class STUDENT that inherits from PERSON , then STUDENT would inherit that slot changing SELF for STUDENT , so STUDENT would have a list of STUDENT instead of PERSON .

Can that be done in Scala? I can´t find out anything about that.

Thanks!

There is an idiom for this, and it is used extensively in the collections framework (in all the *Like classes, eg TraversableLike). You need to add the self-type as a type parameter (like possible in C++ with the CRTP ) of the superclass:

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.
}

After defining this class, we can try defining subclasses in the interpreter:

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]'s selftype Person[Student] with Student
       class Student2 extends Person[Student]

A mistake which is not prevented is having this definition, where Self is not redefined:

scala> class SpecStudent extends Student
defined class SpecStudent

Thanks to the + in front of Self , which makes it a covariant type parameter (I'm not explaining what that is), this however is at least possible:

scala> class SpecStudentCorrect extends Student with Person[SpecStudentCorrect]

scala> (new SpecStudentCorrect).list
(new SpecStudentCorrect).list
res1: Seq[SpecStudentCorrect] = List()

I'm not sure if this will actually be useful to you, but the closest thing I can think of is this.type . Eg:

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()

The this keyword in Scala is more or less equivalent.

When developing extensible software it is sometimes handy to declare the type of the value this explicitly:

Explicitly Typed Self References in Scala
http://www.scala-lang.org/node/124

The singleton types and ETSR's do not solve the problem. I myself was looking for just the same feature in Scala, but apparently it lacks the so-called self-type annotations.

There are circumstances where such self-type annotations could be very useful. Consider an example (adapted from Circular type parameters question example ):

// 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]
}

Let's say you put that into a library. A library consumer should do the following:

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
}

It is allright and everything works quite as expected. The only thing that concerns is that a library consumer is supposed to use the self-bound type parameter (#1 in the code). But that's not all. Now suppose you have some sort of an ActiveRecord pattern in mind, and you want to add the method save to Element , which just delegates to it's container's add method. Surprisingly, it is not that easy:

trait Element[E <: Element[E]] {
  def container: Container[E]
  def save() = container.add(this)   // won't compile
}

found   : Element[E]
required: E

Intuitively, we have a few options here:

  • make add method accept Element[E] instead of E ;
  • cast this to Element[E] .

None of these options are satisfactory, just because of the fact that E is not the same as Element[E] (implementations are not forced to use self-bound type parameters). The only way I see of solving this problem is to have that self-type concept in Scala (let's suppose we have it in our favorite language):

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
}

If the compiler could treat this (or maybe another keyword), when it is used inside square brackets, as the type of the actual implementation (ie the same type as the result of obj.getClass ), then the problems would disappear.

PS May someone consider including this stuff into Scala wishlist? Unfortunately, I don't know, how hard it is to implement such logic since there could be the problems with the notorious JVM's erasure.

PPS Or maybe there is some another Scala-way I'm unaware of?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM