Reputation: 6621
I'm trying to achieve F-bounded polymorphism without using generics. I also need to use self-typing as I will be referencing this
and expecting it to be typed as the subtype.
trait MyTrait[T] { self: Self => // Compilation error: cannot reference 'Self'
type Self <: MyTrait[T]
def doSomethingWithSubtype() {
...
}
}
I can achieve this quite easily using type parameters (i.e. generics), but would like to know if I'm missing something to make the above compile. Can you use abstract types in this way?
Similar questions:
These provide workarounds for similar problems, leading me to believe the above is impossible?
F-Bound Polymorphism with Abstract Types instead of Parameter Types?
F-bounded quantification through type member instead of type parameter?
Upvotes: 3
Views: 113
Reputation: 11290
You can self-type to an abstract type, with one tricky limitation: it has to be defined outside of your trait, but still in scope in a way that allows implementations to implement it with some type. You can do that by wrapping the whole thing into a trait:
trait MyTraitSystem {
type TraitImpl <: MyTrait
trait MyTrait { self: TraitImpl =>
def doSomething(t: TraitImpl): String
}
}
// with an example implementation of the trait:
object MyImpl extends MyTraitSystem {
case class TraitImpl(data: String) extends MyTrait {
def doSomething(t: TraitImpl): String = t.data + " " + data
}
}
This is equivalent to this version using a type parameter:
trait MyTrait[T <: MyTrait[_]] { self: T =>
def doSomething(t: T): String
}
// with an example implementation of the trait:
case class TraitImpl(data: String) extends MyTrait[TraitImpl] {
def doSomething(t: TraitImpl): String = t.data + " " + data
}
Apart from an import MyImpl._
for the abstract-type version, they can be used the same way:
scala> import MyImpl._
import MyImpl._
scala> val a = TraitImpl("hello")
a: MyImpl.TraitImpl = TraitImpl(hello)
scala> val b = TraitImpl("world")
b: MyImpl.TraitImpl = TraitImpl(world)
scala> b.doSomething(a)
res0: String = hello world
The abstract-type version is more verbose, but it works. You also need to carry around a MyTraitSystem
in any method/class/... that needs to use TraitImpl
so as to provide the type:
object SomewhereElse {
def doSomethingElse(s: MyTraitSystem)(t: s.TraitImpl) =
??? // s.TraitImpl is the implementation type
}
Compared to the type parameter version:
object SomewhereElse {
def doSomethingElse[T <: MyTrait[_]](t: MyTrait[T]) =
??? // T is the implementation type
}
This is probably only one of several ways to do this, but I don't think any way can match the conciseness of the type-parameter-based version.
Upvotes: 5