【问题标题】:Return generic superclass from a function instead of subclass从函数而不是子类返回通用超类
【发布时间】:2018-06-08 14:30:28
【问题描述】:

如果我有一个抽象类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]

【问题讨论】:

    标签: scala generics


    【解决方案1】:

    这种类或特征及其子类的参数化称为 F 有界多态性。当您想要具有诸如位置之类的特征时,它很有用,但您希望其字段返回位置的实际子类型,而不是通用位置本身。这在例如构建器模式。

    在你的情况下,就像

    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,它的返回类型是任何 Location 参数化的。

    通过这种方式,您可以获得一些通用代码,该代码适用于类型为Location 的某个值v,但调用V.getLoc 将返回您定义v 时使用的实际位置子类型。所以如果你有val v: Location[Berlin],你可以获得柏林类型的v.getLoc,而不是获得更通用的类型Location,这很方便。

    但是,您的示例似乎不属于这种性质。 你的布尔值让事情变得复杂;你不能有一个“有时是 Loc1,有时是 Loc2,这取决于你如何参数化函数”的返回类型。如果我打电话给getLoc[Loc1](false) 怎么办?只要你有那个布尔值,你就不能指定返回类型,即使是 F 有界的多态性也是如此。您必须对您的用户诚实,并承认您可以安全地说返回类型是某个位置。但是为此您甚至不需要 FBP,您可以简单地说:

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

    您也可以使用求和类型,这归结为与子类型相似的原理。所以你会说你的返回类型是Either[Loc1, Loc2]。 (Scala 3 将允许真正的联合,所以你可以说Loc1 | Loc2)。但是你不能说“在这里,用 L 参数化你的方法,这将是返回类型”,让用户错误地希望他们可以控制返回类型并且它实际上将是他们想要的任何 L,然后你使用这个邪恶的布尔值,它可以将返回类型绑定到其他东西。

    【讨论】:

      【解决方案2】:

      由于您在编译时不知道x 是什么,因此您会丢失有关返回值的确切类型信息。您再也无法将Loc1Loc2 区分开来。您只知道对于某些类型L,返回的类型是Location[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")
      

      【讨论】:

      • 感觉(对我来说)就像存在主义一样,因为这不是这里的方法。在我看来,OP 有点XY problem
      • @YuvalItzchakov 同意:目前还不清楚为什么有人要引入一个相当重量级的 F 有界多态性(用于跟踪有关类型的精确信息),然后立即丢弃所有类型信息并使用存在...
      猜你喜欢
      • 2018-05-09
      • 1970-01-01
      • 2013-04-14
      • 1970-01-01
      • 1970-01-01
      • 2011-09-05
      • 2016-04-05
      • 1970-01-01
      • 2011-07-23
      相关资源
      最近更新 更多