nicolastpi01
nicolastpi01

Reputation: 87

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

Upvotes: 3

Views: 102

Answers (4)

Steve Waldman
Steve Waldman

Reputation: 14083

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)

Upvotes: 2

Hosam Aly
Hosam Aly

Reputation: 42474

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

Upvotes: 0

sarveshseri
sarveshseri

Reputation: 13985

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)

Upvotes: 1

Steve Waldman
Steve Waldman

Reputation: 14083

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

Upvotes: 0

Related Questions