【问题标题】:Use self type as return type in Scala trait在 Scala trait 中使用 self 类型作为返回类型
【发布时间】:2019-07-25 10:55:09
【问题描述】:

我想定义一个特征,它使用具体类的类型,在其抽象方法之一中将其扩展为返回类型。这在 Scala (2.13) 中可行吗?例如,以下内容无法编译,因为我找不到绑定ConcreteType 的方法:

trait Shape
trait Growable {
  def grow() : ConcreteType
}
case class Circle(x : Int, y : Int, size : Int) extends Shape with Growable {
  def grow() : Circle = this.copy(size = size + 1)
}
case class Square(x : Int, y : Int, size : Int) extends Shape with Growable {
  def grow() : Square = this.copy(size = size + 1)
}

我用以下代码实现了一些接近:

trait Shape
trait Growable[T <: Shape] {
  def grow() : T
}
case class Circle(x : Int, y : Int, size : Int) extends Shape with Growable[Circle] {
  def grow() : Circle = this.copy(size = size + 1)
}
case class Square(x : Int, y : Int, size : Int) extends Shape with Growable[Square] {
  def grow() : Square = this.copy(size = size + 1)
}

然后这个代码的用户会这样使用它:

val circle : Circle = Circle(0, 0, 10).grow()
val square : Square = Square(0, 0, 10).grow()
// or
val shapes : Seq[Shape] = List(circle, square).map(_.grow())

我想避免通过泛型传递类型,这似乎是多余的。关于如何实现这一点的任何想法?

【问题讨论】:

  • 我对 “不正确” 答案的数量感到惊讶,因为这是 Scala 中非常常见的问题。 This blog post from Rob Norris详细解释问题和解决方案。 - 长话短说;博士;最简单的解决方案是使用F-Bounded 多态(与您的解决方案类似),但它并不完全安全。最好的解决方案是使用 typeclasses 而不是 subtype 多态性。 - 顺便说一句,如果你有兴趣,我写了一个简单的article 比较这两个
  • 请注意,马里奥的回答包括我提到的 typeclasses 方法。

标签: scala traits type-systems


【解决方案1】:

考虑shapeless lenses 方法

import shapeless._

sealed trait Shape
case class Circle(x : Int, y : Int, size : Int) extends Shape
case class Square(x : Int, y : Int, size : Int) extends Shape

implicit val circleLens = lens[Circle].size
implicit val squareLens = lens[Square].size

implicit class GrowableShape[T <: Shape](shape: T) {
  def grow()(implicit shapeLense: Lens[T, Int]): T =
    shapeLense.modify(shape)(_ + 1)
}

Circle(0, 0, 10).grow()
Square(0, 0, 10).grow()

哪个输出

res0: Circle = Circle(0,0,11)
res1: Square = Square(0,0,11)

或者考虑使用 vanilla scala 的类型类解决方案

trait Growable[T <: Shape] {
  def grow(shape: T): T
}

sealed trait Shape
case class Circle(x : Int, y : Int, size : Int) extends Shape
case class Square(x : Int, y : Int, size : Int) extends Shape

implicit val circleGrowable = new Growable[Circle] {
  def grow(shape: Circle): Circle = shape.copy(size = shape.size + 1)
}

implicit val squareGrowable = new Growable[Square] {
  def grow(shape: Square): Square = shape.copy(size = shape.size + 1)
}

implicit class GrowableShape[T <: Shape](shape: T) {
  def grow()(implicit growable: Growable[T]): T =
    growable.grow(shape)
}

Circle(0, 0, 10).grow()
Square(0, 0, 10).grow()

哪个输出

res0: Circle = Circle(0,0,11)
res1: Square = Square(0,0,11)

【讨论】:

  • 我认为您需要一个充分的理由来证明所有这些隐含的合理性。此外,您的 grow 方法的来源也更难理解。即使这看起来是一个不错的解决方案,我也不会使用它,如果我希望其他(未来)没有经验的开发人员必须阅读它。
【解决方案2】:

以最简单的方式,由于 scala 方法/函数结果类型本质上是协方差的,即 () =&gt; Growable() =&gt; Circle() =&gt; Square 的超类型,您可以通过在实现中显式提供具体类型来简单地执行以下操作:

  trait Shape
  trait Growable {
    def grow() : Growable
  }
  case class Circle(x : Int, y : Int, size : Int) extends Shape with Growable {
    def grow() : Circle = this.copy(size = size + 1)
  }
  case class Square(x : Int, y : Int, size : Int) extends Shape with Growable {
    def grow() : Square = this.copy(size = size + 1)
  }

【讨论】:

  • 虽然这适用于简单的情况,但与更抽象的方法结合使用时,它不会具有所需的语义。就像尝试编写一个通用方法doubleGrow 一样,它接收任何Shape 并返回相同类型的Shape,但在调用grow 两次之后。
猜你喜欢
  • 1970-01-01
  • 2020-11-23
  • 1970-01-01
  • 2021-02-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-03
  • 2018-08-15
  • 2021-12-13
相关资源
最近更新 更多