从函数而不是子类返回泛型超类

时间:2018-06-08 14:30:29

标签: scala generics

如果我有一个抽象类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,但会看到Loc1Loc2。我怎样才能让它返回特定的子类,但作为超类LLocation[L]

2 个答案:

答案 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")