Returning the same type from abstract traits method
Let's say that we have a trait that has some meanings and some operations on them.
trait Foo {
type Self <: Foo
val x: Int
def withX(x: Int): Self
}
This is implemented using abstract types. We have a type binding to Self and can implement it like this:
case class Foo1(x: Int) extends Foo {
type Self = Foo1
def withX(x: Int) = copy(x = x)
}
It's fine. We can use the method and we can see that the type is statically persisted.
scala> Foo1(10).withX(5)
res0: Foo1 = Foo1(5)
Problems start when we want to have an operation on a type rather than a specific type:
object Foo {
//Error:(13, 43) type mismatch;
//found : f.Self
//required: A
// def setFive[A <: Foo](f: A): A = f.withX(5)
}
Well, we can't do that because the compiler doesn't know what type of Foo # Self will be assigned. But we know that this is the same type.
Of course, using the ugly approach works great:
object Foo {
// Ugly type signature
def setFiveValid[A <: Foo](f: A): A#Self = f.withX(5)
// Another ugly type signature
def setFiveValid2[A <: Foo](f: A): f.Self = f.withX(5)
}
None of them express intent very clearly.
We can work with it using type classes.
case class Foo2(x: Int)
trait FooOps[A] extends Any {
def a: A
def withX(x: Int): A
}
object Foo2 {
implicit class Foo2Ops(val a: Foo2) extends AnyVal with FooOps[Foo2] {
def withX(x: Int) = a.copy(x = x)
}
}
object Foo {
// View bounds approach.
def setFiveValid3[A <% FooOps[A]](f: A): A = f.withX(5)
}
However, it is still very noisy.
Is there a better way to implement this setFive
?
Edit 1
The main problem with native types is this:
Error:(24, 11) type mismatch;
found : app.models.world.WObject.WorldObjUpdate[self.Self] => app.models.world.WObject.WorldObjUpdate[self.Self]
(which expands to) app.models.game.events.Evented[(app.models.world.World, self.Self)] => app.models.game.events.Evented[(app.models.world.World, self.Self)]
required: app.models.world.WObject.WorldObjUpdate[self.Self] => app.models.game.events.Evented[(app.models.world.World, Self)]
(which expands to) app.models.game.events.Evented[(app.models.world.World, self.Self)] => app.models.game.events.Evented[(app.models.world.World, Self)]
identity
^
Which then resort to weird looking signatures and patterns:
def attackReachable(
data: WObject.WorldObjUpdate[Self]
): WObject.WorldObjUpdate[data.value._2.Self]
source to share
The best signature is the path-dependent type you suggested:
// Another ugly type signature
def setFiveValid2[A <: Foo](f: A): f.Self = f.withX(5)
You don't even need a type parameter. Enter f
as Foo
(unless you need it A
for something else in your real context):
def setFiveValid3(f: Foo): f.Self = f.withX(5)
It's not ugly. This is one of the ideal uses for path dependent types, conversely. I also disagree when you say that it does not explicitly express intent: you are clearly stating that the result will be of the type of Self
argument you give.
source to share