Proposal : Abstract `This` type inside every class/trait


#1

I find myself many times wanting to refer to the current class/trait type, but not the type of the instance (not this.type). For example:

trait Foo[T] {
  type This <: Foo[T]
}
trait Bar[T1, T2] extends Foo[T1] {
  type This <: Bar[T1, T2]
}

I propose that a This abstract type will be added to Any and its upper bound automatically set by the compiler for every new trait/class definition which is not anonymous.


#3

It’s worth thinking about it.

I fear if This is always an abstract type it would not be very useful. You could not use it as a type of a parameter, for instance.

Also, quite a few class hierarchies already define This. What should happen in this case?


#4

I wanted to give an example, and then realized my proposal was incomplete.
Say I want to create a generic print method that should have an implicit Printer at scope.

trait PrinterOf[T] {
  def apply(that : T) : Unit
}
trait MyGeneric {
  type This <: MyGeneric
  def print()(implicit print : PrinterOf[this.type]) : Unit = print(this)
  def printThis()(implicit print : PrinterOf[This]) : Unit = print(this.asInstanceOf[This])
}
class Foo[T] extends MyGeneric {
  type This <: Foo[T]
}

object Test {
  implicit def printerOfFoo[T] : PrinterOf[Foo[T]] =  foo => println("I pity the Foo")
  val myFoo = new Foo[Int] {
    type This = Foo[Int]
  }
  myFoo.printThis() //works
  myFoo.print() //fails
}

There may be issues with variance annotations, I realize that. Any other ideas?


#5

So my proposal is that This remains abstract until when its parent is instantiated, where This will be completed as the most constrained upper-bound, which is not this.type.


#6

I’m not exactly sure what you’re referring to. Here is an example with inheritance from two traits with This defined:

trait PrinterOf[T] {
  def apply(that : T) : Unit
}
trait MyGeneric {
  type This <: MyGeneric
  def print()(implicit print : PrinterOf[this.type]) : Unit = print(this)
  def printThis()(implicit print : PrinterOf[This]) : Unit = print(this.asInstanceOf[This])
}
trait Foo1[T] extends MyGeneric {
  type This <: Foo1[T]
}
trait Foo2[T] extends MyGeneric {
  type This <: Foo2[T]
}
trait CombinedFoo[T] extends Foo1[T] with Foo2[T] {
  type This <: CombinedFoo[T]
}

object Lab1 extends App {
  implicit def printerOfFoo[T] : PrinterOf[CombinedFoo[T]] =  foo => println("I pity the Foo")
  new CombinedFoo[Int]{
    type This = CombinedFoo[Int]
  }.printThis()
}

#7

In your example it is enough to make PrinterOf contravariant, i.e. to have it being declared as

trait PrinterOf[-T] {
  def apply(that: T) : Unit
}

Are you sure, your printer must be invariant and at the same time you need to have instances of anonymous classes that use this printer?


#8

This could also be the name of inheritable constructors.
For java collections this feature would be useful,

since it would express&implement this requirement:

All general-purpose Collection implementation classes
(which typically implement Collection indirectly through one of its subinterfaces)
should provide two “standard” constructors: a void (no arguments) constructor,
which creates an empty collection,
and a constructor with a single argument of type Collection,
which creates a new collection with the same elements as its argument.

I am not sure how useful&feasible inheritable constructors would be for Scala.


#9

In the example above, the idea is to have unique (invariant) printers and not a general one.


#10

If you already have to provide an implicit instance for a particular parameter type, then you might as well figure out which methods will receive it and manually set the argument type for these methods.


#11

Thanks, good question.
Currently I write somethink like this:

  trait Nullable[V  <: AnyRef , T<:Nullable[V,T] ] extends Any{
    def === (that: T): Boolean

    def newInstance(v:V): T
    def clone(): T
    //Some others method
  }

But It is not very convenient.


#12

Interesting, few months ago I was trying to discuss something very similar in my post about clone-like methods
Effectively it was a call for something very similar to proposed here This-type

Actually that post was quite verbose, but in general it was suggested 2 approaches to solve this problem:

  • make This type concrete only in final classes (as it was actually done in Rust as far as I know/understand)
  • make some possibility to somehow declare factory method which calls constructor indirectly - but this approach should also introduce some new form of constraint - non abstract sub class of that constraint class should always have that constructor, with signature matching to intended factory method

#13

I forgot about this. Indeed, very annoying and always bothered me.


#14

There two rules, one simple and one complex, together allow a my-type This to work.

Simple: In argument position, subclasses must overload the method to provide a child-class-specific variant.

Complex: This-typed returns are marked with their actual return type from the implementation. A subclass for which the actual return type is no longer consistent with the subtype must override the method to provide a consistent return type.

Example:

class Parent[+A](val a: A, val aa: A) {
  def arg(me: This[A]): Unit = { println(a) }
  def change: This[A] = new Parent[A](aa, a)
  def twice: This[A] = change.change
}

class Child[+A <: Seq[Int]](val value: A) extends Parent(value, value)[Seq[A]] {}
// Compile error: method arg not overloaded with subclass Child
// Compile error: method change has incompatible return type in superclass implementation
//   Found return type:    Parent[A]
//   Required return type: Child[A] (as This[A])

class Child(val value: Seq[Int]) extends Parent(value, value)[Seq[Int]] {
  def arg(me: Child): Unit = arg(me: Parent[Seq[Int]])
  def change: Child = new Child(value.reverse)
  // method twice has true This[A] return type, so is okay
}

class Grandchild extends Child(Seq(1, 2, 3)) {}
// Compile error: method arg not overloaded with subclass Grandchild as required by inheritance from Parent
// Compile error: method change has incompatible return type from inheritance from Parent
//   Found return type:    Child
//   Required return type: Grandchild
//     Requirement for This return type from definition in Parent

This would let the compiler verify and help with a huge amount of the work of keeping self-types straight.


#15

Did you mean to have This have a type arguments ([A])? Not all inheritors have the same type-argument signature as their parents.