[英]Returning the same type from abstract trait method
Lets say we have a trait, that has some values 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 bound on Self and can implement it like this:
我们在Self上有一个绑定类型,可以像这样实现它:
case class Foo1(x: Int) extends Foo {
type Self = Foo1
def withX(x: Int) = copy(x = x)
}
That is fine. 那样就好。 We can use the method and we see that the type is statically kept.
我们可以使用该方法,我们看到该类型是静态保留的。
scala> Foo1(10).withX(5)
res0: Foo1 = Foo1(5)
Problems start when we want to have an operation with trait type, not the concrete 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 quite do it, because the compiler does not know what type Foo#Self will be assigned to. 好吧,我们做不到,因为编译器不知道Foo#Self将被分配给什么类型。 But we know it is the same type.
但我们知道它是同一类型。
Of course using an ugly approach works fine: 当然使用丑陋的方法工作正常:
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)
}
Neither of them express intent very clearly. 他们都没有非常清楚地表达意图。
We can work around it using typeclasses though. 我们可以使用类型类来解决它。
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 this is still very noisy. 然而这仍然非常嘈杂。
Is there a better way to implement setFive
? 有没有更好的方法来实现
setFive
?
Edit 1 编辑1
The main issue with self types are things like 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 boilerplate again: 然后再次采用奇怪的签名和样板:
def attackReachable(
data: WObject.WorldObjUpdate[Self]
): WObject.WorldObjUpdate[data.value._2.Self]
You could go down the "F-bounded quantification" road: 你可以走“F-bounded quantication”之路:
trait Foo[F <: Foo[F]] {
def withX(x: Int): F
}
object Foo {
def setFive[F <: Foo[F]](f: F): F = f.withX(5)
}
I used this a lot with success, but it comes at the price of having to write F <: Foo[F]]
everwhere. 我成功地使用了这个,但它的代价是不得不写
F <: Foo[F]]
。
The best signature is the path-dependent type that you suggested: 最佳签名是您建议的路径依赖类型:
// Another ugly type signature
def setFiveValid2[A <: Foo](f: A): f.Self = f.withX(5)
You don't even need the type parameter. 你甚至不需要type参数。 Typing
f
as Foo
will do (unless you need A
for something else in your real context): 输入
f
作为Foo
会做(除非你在实际环境中需要A
代替其他东西):
def setFiveValid3(f: Foo): f.Self = f.withX(5)
This is not ugly. 这不难看。 It's one of the perfect uses for path-dependent types, on the contrary.
相反,它是路径依赖类型的完美用途之一。 I also disagree when you say that it doesn't express the intent clearly: you're clearly stating that the result will have the type
Self
of the argument that you're giving. 当你说它没有明确表达意图时,我也不同意:你明确表示结果将具有你所给予的论证的类型
Self
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.