type inference & generics in scala

I'm doing the following function to return a new figure from another, but Scala is inferring the result as Figure and I want it to be the figure in particular, as a circle, etc. How could I do to infer the particular figure? I have been told to use generics to solve it, how would this be?

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

4 answers

  • answered 2017-12-11 05:59 Steve Waldman

    Maybe what you are after is something like

    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]
      }
    }
    

  • answered 2017-12-11 05:59 Sarvesh Kumar Singh

    You should make it so that the "move" happens on the type T itself and return type T. But then the compiler will complain about not being sure that you are returning a T because the actual type of T will be determined for the use of move and compiler has no evidence to determine that it was a Circle as match-case is a runtime thing.

    Which means you need to provide evidence which can be used at compile-time to move any instance of 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
    

    Now, let us build the required evidence which will be used to "enrich" our Figure instances

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

    Now, we can use these evidence to "enrich" our Figure types to have move support.

    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)
    

    Or, you can build your Motor using these evidence.

    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)
    

  • answered 2017-12-11 05:59 Hosam Aly

    You may want to consider moving the implementation of move to the various classes. Here is an example that uses abstract types to enable the method to return the type of the object:

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

  • answered 2017-12-11 05:59 Steve Waldman

    Here's is a more concise, perhaps a bit less intimidating version of Sarvesh Kumar Singh's suggestion to use a typeclass. I think that is the best approach all around. It gives you typesafe functionality while letting you keep your basic types very simple.

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

    Then...

    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)