Actually my initial thought was about 2 things that are currently missing in Scala “generics system”
- Abstract type could not “fully replace” trait’s/class’es generic parameters, in particular they could not participate in Self-type statements. Hoover abstract types looks much more pleasant when one need to work with wildcards.
So it would be good to have some possibility to define some “abstract outer types” - which should be much close to generic parameters, could participate in Self-type statements but would be defined with syntax modifier type T
inside trait/class body instead of [T]
in trait/class heading
- There is no way to define Self-type with
public
visibility. For now it is always like it has protected
visibility (so that constraint is only visible from withing enclosing trait/class), while in certain cases it is quite logical to publicly expose that constraint (so that make it visible everywhere, not only inside enclosing trait/class)
(see also issue#5880 - call for public
Self-types)
So my initial thinking was around possibility to rewrite working Scala code
// - invariant form ; covariant form with `PThis <: HasThisType[_ <: PThis]` works well in Dotty, but in Scala is more verbose
trait HasThisType[PThis <: HasThisType[PThis]] {
this: PThis =>
type This = PThis
}
// so that covariant form in Scala may look like following:
type HasThisTypeBounded[PThis] = HasThisType[_ <: PThis]
trait HasThisType[PThis <: HasThisTypeBounded[PThis]] {
this: PThis =>
type This = PThis
}
Into something like
type HasThisTypeBounded[PThis] = HasThisType {
type This <: PThis
}
trait HasThisType {
// hypothetical usage of `public` keyword with self-types statment
public this: This =>
@IsGenericParameterType(jvmName = "ParamThis")
type This <: HasThisTypeBounded[This]
}
Assuming that later one snippet on JVM/bytecode level could be converted into something like
type HasThisTypeBounded[PThis] = HasThisTypeInJvm[_ <: PThis]
trait HasThisTypeInJvm[ParamThis <: HasThisTypeBounded[ParamThis]] {
this: ParamThis =>
type This = ParamThis
}
But essential difference should be in wildcard usages - more specifically HasThisType
should rather be treated as HasThisTypeInJvm[_]
wildcard. But in general HasThisType
should behave like regular abstract class/trait with some abstract type inside (the only limitation to that @IsGenericParameterType
-types should be that they should not refer to regular abstract type from immediately enclosing class/trait - in should only be allowed to point to other abstract types (from that class) which also marked with @IsGenericParameterType
modifier)
When HasThisType
derived by some other trait/class it should also automatically derive that ParamThis
type/parameter
@IsGenericParameterType(jvmName = "ParamThis")
type This
Or sub-class/sub-trait could make it not-abstract type by assigning it some appropriate (valid in that context) type expression
So my initial thought was around public form of Self-type and some sugar for wildcards (in form of formal conversion from generic parameters to some special abstract types).
However when I dig bit dipper into this question, I found some alternative form of HasThisType
implementation, which actually bases on some feature “dual” to Self-type - in particular it looks that upper-bounded abstract type type This >: this.type
could perfectly fit general requirements of HasThisType
definition.
type HasThisTypeLoverBounded[PThis] = HasThisType { type This <: PThis }
trait HasThisType {
type This >: this.type <: HasThisTypeLoverBounded[This]
// just for testing purposes
// inline
def self(): This = this
}
Here is nice and remarkable thing, that it does not use Self-type statement at all.
So that if we have some that: HasThisType
it will obviously provides that.type <: that.This
which is “not so obvious” to achieve using Self-type-approach (which will provide that only for this
, like this.type <: this.This
).
But looking closer to this code one may find multiple issues.
For example consider demonstration nippet
Shortly it looks like
// this code is compileble in Scala while in Dotty it makes compiler runtime crash
type FooLikeTypeLoverBounded[PThis] = FooLike { type This <: PThis }
abstract class FooLike extends HasThisType {
type This >: this.type <: FooLikeTypeLoverBounded[This]
def doSmth(): This
def doSmth2(): This = self().doSmth().doSmth()
def applySmth(arg: This): This
def applySmth2(arg: This): This = /*doSmth2().*/applySmth(arg.doSmth2()).doSmth2()
}
case class Foo(any: Any) extends FooLike {
type This = Foo
override def doSmth(): This = Foo(s"doSmth($this)")
override def applySmth(arg: This): This = Foo(s"($this)applySmth($arg)")
}
It basically works in Scala (but if you switch to Dotty compiler you’ll see significant problems, also this problem is not yet fixed in current state of Dotty master
branch). (see issue#5876)
In contrast to Dotty, Scala treats that code +/- correctly (in code inside body of classes/traits everything works fine, but from outside it sometimes works as expected, and sometimes - NOT)
But I guess it is worth to extract that into separate topic with more detailed description of my attempts to encode HasThisType
trait (with all issues that I’ve faced on that way)
Eventually may say that I am hoping that Dotty fix will be not in disabling this.type
as upper boundary of abstract type, but rather it would be proper/positive treatment of that cases.