scala - 当由子类型使用时,对泛型方法的类型参数进行泛型子类型化

标签 scala generics

我正在尝试以一种干净的方式实现以下代码架构。

有一个特征(也可以是抽象类,但我不认为它会解决这个问题)我称之为 Shape ,由两个类子类型,CircleRectangle .他们每个人都提供自定义成员:一个 Shape 有一个(抽象)方法 size允许更改大小(并由子类实现);一个圆有一个 radius field ;一个矩形有一个 width和一个 height字段。

然后,有一个通用特征,Changer ,它允许对形状进行修改。它是通用的,Changer[-S <: Shape] , 并由三个不同的类进行子类型化,SizeChanger改变 Shape 的大小, WidthChanger改变 Rectangle 的宽度, 和 RadiusChanger改变 Circle 的半径.

每个Changer有一个方法 change(shape)实际执行 Changer 指定的更改.

最后有个方法change(shapeChanger)Shape本质上包括调用 change 的类形状的方法Changer用这个对象。

总而言之,代码如下所示:

trait Shape {
  def change(changer: /* This is the question */): Unit = {
    changer.change(this)
  }
  def size(s: Int): Unit
}
class Circle(var r: Int) extends Shape {
  override def size(s: Int): Unit = r = s/2
}
class Rectangle(var w: Int, var h: Int) extends Shape {
  override def size(s: Int): Unit = {
    w = s
    h = s
  }
}

trait Changer[-S <: Shape] {
  def change(shape: S): Unit
}
class Size(s: Int) extends Changer[Shape] {
  override def change(shape: Shape): Unit = shape.size(s)
}
class Radius(r: Int) extends Changer[Circle] {
  override def change(shape: Circle): Unit = shape.r = r
}
class Width(w: Int) extends Changer[Rectangle] {
  override def change(shape: Rectangle): Unit = shape.w = w
}

这些类的使用示例:

val circle = new Circle(42)
val rectangle = new Rectangle(10, 20)
val rectAsShape: Shape = rectangle
val widthChanger = new Width(30)
val radiusChanger = new Radius(17)
val sizeChanger = new Size(40)
val radAsChanger: Changer[Circle] = radiusChanger
val sizeAsChanger: Changer[Shape] = sizeChanger
val sizeAsCircleChanger: Changer[Circle] = sizeChanger
circle.change(radiusChanger)
circle.change(sizeChanger)
circle.change(sizeAsCircleChanger)
rectangle.change(widthChanger)
rectangle.change(sizeChanger)
rectangle.change(sizeAsChanger)
rectAsShape.change(sizeChanger)
// rectAsShape.change(widthChanger) // Shouldn't work

Changer性状 S参数在技术上被指定为逆变的 SizeChanger (和所有 Changer[Shape] )也是有效的 Changer[Circle] ,因为他们知道如何处理 Shape ,例如 Circle .

真正的问题是 change Shape 中的方法特征。实际上,Shape 的每个子类看起来总是一样的。并且只会调用更换者。所以它应该是通用的,不要期望 Changer[Shape] ,而是一个 Changer[S] , 其中S是具体对象的类型。这样,一个Circle可以接受更具体的Changer[Circle]我们仍然可以将它转换为 Shape并使用 Changer[Shape] 调用该方法.

我尝试了多种类型参数的不同组合,使用了各种变体和类型界限,但我没有得到正确的组合。

幸运的是,我发现以下定义有效:

def change(changer: Changer[this.type]): Unit = ???

但我正在寻找一种更通用的方法。

我很确定这个问题是关于 SO 的其他问题的重复,因为这种模式在我看来很常见,但由于我无法在这种模式上命名,所以我没有找到任何有效或有用的东西(目前)。

因此,任何关于这种代码模式的名称(或关于这种代码不好的方面的任何意见)都会有所帮助。

编辑 1:

也许我可以补充说最终目标是在 DSL 中使用所有这些(通过 Scala.js 用于 Canvas API)最终看起来像

circle1 and circle2 change Radius(10)
circle1 and circle2 and rectangle1 change StrokeColor(Color.red) and StrokeWidth(4)
// This changes the 3 shapes' stroke color and stroke width.

要求用户每次想要更改形状的属性时都“创建”一个新变量是很麻烦的,因此必须在内部更改形状。

此外,因此会有一些返回类型而不是 Unit对于 Shape::change方法,尽管我(好吧,我们)仍然需要弄清楚。

最佳答案

好吧,我想我的回答会相当冗长,但既然您已经就代码的所有方面征求了意见,我希望这个解释能帮助您理解 Scala 方法的一般哲学。

首先,在 Scala 中,人们通常会尽量避免使用任何可变数据结构。这是为什么?让我们检查 Changer 类中的 change 方法的签名。它需要一些东西 (Shape) 并返回 Unit。可以相当假设 ChangerShape 本身不做任何事情,因为 Unit 除了“代码块完成”之外没有发出任何信号。所以第一步是将签名更改为 change(shape: S): S 以表明我们的意图。

好的,现在我们显然正在进行一些修改,但是我们可以通过从 CircleRectangle< 中删除所有 var 来进一步改进代码 并使它们成为 case classes .案例类是 Scala 中用于定义不可变数据结构的简洁概念。我们无法改变它们的状态,但我们可以创建具有新状态的新状态。定义很简单:

case class Circle(r: Int) extends Shape { ... }
case class Rectangle(w: Int, h: Int) extends Shape { ... }

案例类允许我们使用方便的 copy 方法,您可以从我上面提供的链接中了解该方法。所以,现在,当我们不需要改变任何 Shape 时,我们可以从基本特征中删除 change 方法。但是,当然,这意味着我们失去了方便的 obj.change(...) 语法。如何应对这一问题?

答案是类型类模式。有一个neat post Rob Norris 详细解释了该模式。我们已经具备了实现它的一切条件。我们只需要写一个简短的 implicit class这基本上是一个语法扩展。

我将提供您能想到的最基本的:

implicit class ChangerOps[S <: Shape](s: S) {
    def change(implicit c: Changer[S]): S = c.change(s)
}

然后您只需将SizeRadiusWidth声明为implicit val(您可以阅读about implicits here ) 并使用您刚刚编写的语法。因为一次确认所有这些信息可能有点困难,所以我准备了一个 code example你可以在线摆弄。请注意,这种方法忽略了逆变,因为您可以扩大类型以获得所需的隐式。

关于scala - 当由子类型使用时,对泛型方法的类型参数进行泛型子类型化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56010257/

相关文章:

typescript `groupBy` 打字

scala - 如何从序列创建受约束的 HList?

java - 在 Java 中创建通用 ArrayList 的 ArrayList

Scala - 组织单例对象层次结构的正确方法是什么?

Scala 泛型函数混淆

c# - 如何编写指定创建逻辑的接口(interface)或抽象类?

java - 将 Class[] 转换为泛型?

java - 没有明确声明泛型类型的赋值如何被滥用?

Scala:值分割不是 char 的成员

scala - 对字符串应用几个转换函数