Also in previous posts (1, 2) where highlighted possible need of some ThisLikeN[T1,...TN]-types (HasThisLikeTypeN traits). Actually approach#1 could not be used here (constraint this.type <: this.ThisLike[T1,...Tn] could not be achieved with regular abstract types, only self-types could provide that in this case).
Meanwhile approach#2 and approach#3 could be easily adapted to this use case.
So starting from this basic skeletons
trait HasThisType1[PThisLike1[_], T1] {
type ThisLike1[P1] = PThisLike1[P1]
}
trait HasThisTypeHolder {
type PThisLike1[_]
trait HasThisType1[T1] {
type ThisLike1[P1] = PThisLike1[P1]
}
}
final implementations may look like described below.
Approach#2
applied to HasThisLikeN / ThisLike[T1, ... ,TN] could look like this
// covariant version
trait HasThisType1[PThisLike1[P1] <: HasThisType1[_ <: PThisLike1, P1], T1] {
this: PThisLike1[T1] =>
type ThisLike1[P1] = PThisLike1[P1]
inline
def self(): ThisLike1[T1] with this.type = this
}
trait HasThisType2[PThisLike2[P1,P2] <: HasThisType2[_ <: PThisLike2, P1, P2], T1,T2] {
this: PThisLike2[T1,T2] =>
type ThisLike2[P1,P2] = PThisLike2[P1,P2]
inline
def self(): ThisLike2[T1,T2] with this.type = this
}
Invariant version (with stronger constraint like #PThisLike1#PThisLike1 =:= #PThisLike1) will become
// invariant version
trait HasThisType1[PThisLike1[P1] <: HasThisType1[PThisLike1, P1], T1] {
this: PThisLike1[T1] =>
type ThisLike1[P1] = PThisLike1[P1]
// inline
def self(): ThisLike1[T1] with this.type = this
}
trait HasThisType2[PThisLike2[P1,P2] <: HasThisType2[PThisLike2, P1, P2], T1,T2] {
this: PThisLike2[T1,T2] =>
type ThisLike2[P1,P2] = PThisLike2[P1,P2]
// inline
def self(): ThisLike2[T1,T2] with this.type = this
}
Again constraint#1 (.type <: .ThisLike1[T1]) and constraint#2 (#ThisLike1[_]#ThisLike1[P1] <: #ThisLike1[P1]) could be easily seen inside class/trait body
(like this.type <: this.ThisLike1[T1], etc), while outside constraint#1 could be exposed through that.self().type <: that.ThisLike[T1] (where that: HasThisType1[T1]), or again alternatively one may define auxiliary type type ThisLikeB <: this.type <: ThisLike1[T1] and expose that as that.type <: that.ThisLikeB <: ThisLike1[T1]
(complete example here) (see also issue#5878, issue#5879)
Bigger snippet that reveals that in Scala, at least inside of enclosing class/trait body that approach works as expected (`this.type <: this.ThisLike1[T1]` and `this.type#ThisLike1[_]#ThisLike1[P1] <: this.type#ThisLike1[P1]`). Briefly it will look like:
trait FooLike[PFooLike[P1] <: FooLike[PFooLike, P1], T1] extends HasThisType1[PFooLike, T1] {
this: PFooLike[T1] =>
def doSmth(): ThisLike1[T1]
def doSmth2(): ThisLike1[T1] = self().doSmth().doSmth()
def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1]
def applySmth2[P1](arg: ThisLike1[P1]): ThisLike1[P1] = doSmth2().applySmth(arg.doSmth2()).doSmth2()
}
case class Foo[T1](any: Any, tag: T1) extends FooLike[Foo,T1] {
override def doSmth(): ThisLike1[T1] = Foo(s"doSmth($this)", tag)
override def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1] = Foo(s"($this)applySmth($arg)", arg.tag)
}
Approach#3
applied to HasThisLikeN / ThisLike[T1, ... ,TN] could look like this
trait HasThisTypeHolder {
// `PThisLike1` serves as generic parameter replacement to make usage in sub classes bit easier
type PThisLike1[P1] <: HasThisLikeType1[P1]
trait HasThisLikeType1[T1] {
this: PThisLike1[T1] =>
type ThisLike1[P1] = PThisLike1[P1]
// inline
def self(): ThisLike1[T1] with this.type = this
}
// `PThisLike2` serves as generic parameter replacement to make usage in sub classes bit easier
type PThisLike2[P1,P2] <: HasThisLikeType2[P1,P2]
trait HasThisLikeType2[T1,T2] {
this: PThisLike2[T1,T2] =>
type ThisLike2[P1,P2] = PThisLike2[P1,P2]
// inline
def self(): ThisLike2[T1,T2] with this.type = this
}
}
Bigger snippet that reveals that in Scala, at least inside of enclosing class/trait body that approach works as expected (`this.type <: this.ThisLike1[T1]` and `this.type#ThisLike1[_]#ThisLike1[P1] <: this.type#ThisLike1[P1]`). Briefly will look like snippet inside
trait FooLikeHolder extends HasThisTypeHolder {
type PThisLike1[P1] <: FooLike[P1]
type PFooLike[P1] = PThisLike1[P1]
trait FooLike[T1] extends HasThisLikeType1[T1] {
this: PThisLike1[T1] =>
def doSmth(): ThisLike1[T1]
def doSmth2(): ThisLike1[T1] = self().doSmth().doSmth()
def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1]
def applySmth2[P1](arg: ThisLike1[P1]): ThisLike1[P1] = doSmth2().applySmth(arg.doSmth2()).doSmth2()
}
}
object fooHolder extends FooLikeHolder {
type PThisLike1[P1] = Foo[P1]
case class Foo[T1](any: Any, tag: T1) extends FooLike[T1] {
override def doSmth(): ThisLike1[T1] = Foo(s"doSmth($this)", tag)
override def applySmth[P1](arg: ThisLike1[P1]): ThisLike1[P1] = Foo(s"($this)applySmth($arg)", arg.tag)
}
}