如果我有一个抽象类Location
和两个子类如下
abstract class Location[L <: Location[L]]
case class Loc1(name: String) extends Location[Loc1]
case class Loc2(name: String) extends Location[Loc2]
def getLoc[L <: Location[L]](x: Boolean): L =
if (x) Loc1("1")
else Loc2("2")
拨打getLoc
会导致类型不匹配,因为它需要L
,但会看到Loc1
或Loc2
。我怎样才能让它返回特定的子类,但作为超类L
或Location[L]
?
答案 0 :(得分:2)
这种类或特征及其子类的参数化称为F-有界多态。当你想要一些特性如Location时,它很有用,但你希望它的字段返回Location的实际子类型,而不是通用的Location本身。这在例如非常有用。建设者模式。
在你的情况下,这就像是
abstract class Location[L <: Location[L]] {
val getLoc: L
}
case class Loc1(name: String) extends Location[Loc1] {
def getLoc: Loc1 = this
}
case class Loc2(name: String) extends Location[Loc2] {
def getLoc: Loc2 = this
}
请注意,Location有一个字段getLoc
,其返回类型是参数化的位置。
通过这种方式,您可以使用某些通用代码,这些代码适用于v
类型的某些值Location
,但调用V.getLoc
将返回您在定义时使用的实际的子类型{ {1}}。因此,如果您有v
,则可以获得柏林类型的val v: Location[Berlin]
,而不是获得更通用的类型v.getLoc
,这很方便。
但是,你的例子似乎并不属于这种性质。 你的布尔值使事情变得复杂; 你不能有一个返回类型“有时是Loc1,有时是Loc2,这取决于你如何参数化函数”。如果我致电Location
怎么办?只要你有这个布尔值,就不能在返回类型中具体,甚至不能使用F-bounded多态。您必须诚实地对待您的用户,并承认您可以放心地说明返回类型是它的位置。但为此你甚至不需要FBP,你可以简单地说:
getLoc[Loc1](false)
你也可以使用sum-type,它归结为与subtyping类似的原则。所以你会说你的返回类型是abstract class Location
case class Loc1(name: String) extends Location
case class Loc2(name: String) extends Location
def getLoc(x: Boolean): Location =
if (x) Loc1("1")
else Loc2("2")
。 (Scala 3将允许真正的工会,所以你可以说Either[Loc1, Loc2]
)。但你不能说“在这里,使用L参数化你的方法,那将是返回类型”,给用户错误的希望,他们可以控制返回类型,并且它实际上将是他们想要的任何L,然后你使用这个邪恶的布尔值,可以将返回类型装入其他东西。
答案 1 :(得分:0)
由于您在编译时不知道x
是什么,因此您将丢失有关返回值的确切类型信息。您无法再从Loc1
分辨Loc2
。您只知道某些类型Location[L]
的返回类型为L
。此类型可以用Scala编写为
Location[L] forSome { type L }
在这种情况下可以缩短为
Location[_]
因此,如果您按如下方式调整getLoc
:
def getLoc(x: Boolean): Location[_] =
if (x) Loc1("1")
else Loc2("2")
然后你的代码再次编译。
以下也有效:
def getLoc(x: Boolean): Location[L] forSome { type L } =
if (x) Loc1("1")
else Loc2("2")