在Scala特性中将自身类型用作返回类型

时间:2019-07-25 10:55:09

标签: scala traits type-systems

我想定义一个特征,它使用具体类的类型将其扩展为其抽象方法之一中的返回类型。 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())

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

3 个答案:

答案 0 :(得分:2)

考虑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)

或者考虑使用香草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)

答案 1 :(得分:0)

afaik,唯一的其他解决方案是改用类型定义

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


object Test {
  def main(args: Array[String]): Unit = {
    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())
    shapes.foreach(println)
  }

}

不要让它更漂亮。

答案 2 :(得分:0)

以最简单的方式,因为scala方法/函数结果类型本质上是协方差的,即() => Growable() => Circle() => 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)
  }