【问题标题】:type inference & generics in scalascala中的类型推断和泛型
【发布时间】:2017-12-11 05:59:48
【问题描述】:

我正在执行以下函数以从另一个返回一个新图形,但 Scala 将结果推断为 Figure 并且我希望它特别是图形,作为一个圆圈等。 我该怎么做才能推断出特定的数字?我被告知使用泛型来解决它,这是怎么回事?

trait Figure {
  def x:Int
  def y:Int
}

case class Circle(x:Int, y: Int, radio: Double)
  extends Figure

case class Rectangle(x:Int, y: Int, width: Int, high: Int)
  extends Figure

object Motor {

  def move[T](x: Int, y: Int, figure: T) :Figure = figure match {
    case Circle(xPos, yPos, radio) => Circle(xPos+x, yPos+y, radio)          
    case Rectangle(xPos, yPos, width, high) => Rectangle(xPos+x, yPos+y, width, high)
  }
}

【问题讨论】:

  • 那么......这段代码有什么问题?除了 Circulo 和 Rectangulo 中的拼写错误(这些是拼写错误吗?)
  • 我假设您在谈论 moveFigure 而不是像 Circle 或 Rectangle 这样的具体类的结果。要访问底层的具体类,您可以像在上面的方法中那样使用模式匹配。
  • @SarveshKumarSingh 函数 move 返回图,但我想特别将图返回为圆形、矩形等。
  • @ThePretendProgrammer 这正是我想要的,但是函数的结果是 Figure 并且我希望它是特定的图形,现在我通过在测试中进行转换来解决它但我想要函数一个人做。

标签: scala


【解决方案1】:

这是 Sarvesh Kumar Singh 关于使用类型类的建议的更简洁、或许不那么吓人的版本。我认为这是最好的方法。它为您提供类型安全的功能,同时让您的基本类型非常简单。

trait Figure {
  def x:Int
  def y:Int
}
case class Circle(x:Int, y: Int, radius: Double) extends Figure

case class Rectangle(x:Int, y: Int, width: Int, height: Int) extends Figure

trait Movable[T] {
  def move( x: Int, y: Int, movable: T ) : T
}
implicit final object CircleIsMovable  extends Movable[Circle] {
  def move( x: Int, y: Int, c: Circle ) = Circle( c.x + x, c.y + y, c.radius )
}
implicit final object RectangleIsMovable  extends Movable[Rectangle] {
  def move( x: Int, y: Int, r: Rectangle ) = Rectangle( r.x + x, r.y + y, r.width, r.height )
}
object Motor {
  def move[T : Movable](x: Int, y: Int, movable: T) : T = implicitly[Movable[T]].move( x, y, movable )
}

那么……

scala> Motor.move(10,10,Circle(0,0,1))
res1: Circle = Circle(10,10,1.0)

scala> Motor.move(10,10,Rectangle(0,0,1,1))
res2: Rectangle = Rectangle(10,10,1,1)

【讨论】:

  • 这看起来不错,我只需将 typeclass 实例移动到 Movable 或 shape 子类型伴随对象中。
  • 是的。通常我会将隐式实例放在 Movable 伴生对象中。但我认为这对于以前可能没有遇到过这种风格的人来说会更吓人。
【解决方案2】:

您应该使"move" 发生在type T 本身上并返回type T。但是随后编译器会抱怨不确定您是否返回T,因为T 的实际类型将由move 的使用确定,并且编译器没有证据确定它是Circle因为match-case 是运行时的东西。

这意味着您需要向move type T 的任何实例提供可在编译时使用的证据。

import scala.language.implicitConversions

trait Figure {
  def x:Int
  def y:Int
}

case class Circle(x:Int, y: Int, radio: Double)
  extends Figure

case class Rectangle(x:Int, y: Int, width: Int, high: Int)
  extends Figure

现在,让我们构建所需的证据,用于“丰富”我们的 Figure 实例

trait MoveSupport[F <: Figure] {
  val f: F
  def move(x: Int, y: Int): F
}

object MoveSupport {

  class CircleMoveSupport(val f: Circle) extends MoveSupport[Circle] {
    override def move(x: Int, y: Int): Circle = f.copy(x = f.x + x, y = f.y + y)
  }

  class RectangleMoveSupport(val f: Rectangle) extends MoveSupport[Rectangle] {
    override def move(x: Int, y: Int): Rectangle = f.copy(x = f.x + x, y = f.y + y)
  }

  implicit def toCircleMoveSupport(circle: Circle) = new CircleMoveSupport(circle)

  implicit def toRectangleMoveSupport(rectangle: Rectangle) = new RectangleMoveSupport(rectangle)

}

现在,我们可以使用这些证据来“丰富”我们的 Figure 类型以获得 move 支持。

import MoveSupport._ 

val circle = Circle(1, 1, 1)
// circle: Circle = Circle(1,1,1.0)

val circle2 = circle.move(1, 1)
// circle2: Circle = Circle(2,2,1.0)

或者,您可以使用这些证据建立您的Motor

object Motor {
  import MoveSupport._

  def move[T <: Figure](x: Int, y: Int, figure: T)(implicit ev: T => MoveSupport[T]): T = figure.move(x, y)

}

val c = Circle(1, 1, 1)
// circle: Circle = Circle(1,1,1.0)    

val c1 = Motor.move(1, 1, c) 
// circle1: Circle = Circle(2,2,1.0)

【讨论】:

    【解决方案3】:

    也许你所追求的是类似的东西

    object Motor {
      def move[T <: Figure](x: Int, y: Int, figure: T): T = {
        val moved = figure match {
          case Circle(xPos, yPos, radio) => Circle(xPos+x, yPos+y, radio)          
          case Rectangle(xPos, yPos, width, high) => Rectangle(xPos+x, yPos+y, width, high)
        }
        moved.asInstanceOf[T]
      }
    }
    

    【讨论】:

    • 看起来不错。为什么[T
    • 这只是将 T 的类型限制为 Figure 或 Figure 的某些子类型。 (下面 Sarvesh Kumar Singh 建议的 typeclass-ish 解决方案更好——它的类型安全得多。在你的方法和我对其的修改中,有人可以在 Figure 的新子类型上调用 move,即 Motor.move(...)不支持,导致运行时错误,我们更喜欢编译时错误。Sarvesh 的解决方案看起来比它需要的更吓人,它可以更简洁地实现。)
    • moved.asInstanceOf[T] 所有的赌注都没有了。我肯定会远离那个。
    • @YuvalItzchakov 我完全同意。我只是试图做出最小的修正以使原始海报使用的方法“起作用”,但这不是最好的方法。 Sarvesh Kumar Singh 的建议要好得多,现在我也发布了一个类似的、更简洁(希望不那么吓人)的方法。
    【解决方案4】:

    您可能需要考虑将move 的实现移至各个类。下面是一个使用abstract types 使方法能够返回对象类型的示例:

    trait Figure {
      def x: Int
      def y: Int
    
      type Self <: Figure
      def move(dx: Int, dy: Int): Self
    }
    
    case class Circle(x: Int, y: Int, radius: Double) extends Figure {
      type Self = Circle
      def move(dx: Int, dy: Int): Circle = copy(x = x + dx, y = y + dy)
    }
    
    case class Rectangle(x: Int, y: Int, widht: Int, height: Int) extends Figure {
      type Self = Rectangle
      def move(dx: Int, dy: Int): Rectangle = copy(x = x + dx, y = y + dy)
    }
    

    【讨论】:

      猜你喜欢
      • 2012-11-29
      • 2023-03-08
      • 2017-09-08
      • 1970-01-01
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-12
      相关资源
      最近更新 更多