Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enhance java classes using traits, how to declare inside trait the java fields?

Tags:

scala

traits

My goal is to enhance inside scala code an existing Java class using a trait mix-in. For example to add a method like java.awt.Rectangle.translate(dx, dy) to java.awt.geom.Ellipse2D class. For this I create the following trait:

trait RectangleLike {
  var x: Double  // abstract vals to correspond to java class fields
  var y: Double  //   I need these vars to refer to them inside translate method
  def translate(dx: Double, dy: Double) {
    x = x + dx
    y = y + dy
  }
  // more concrete trait methods here
} // defines without errors in scala REPL 

Then use the trait when constructing an Ellipse:

val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike

However when I execute the above script in scala REPL I get the following output:

<console>:8: error: overriding variable x in trait RectangleLike of type Double;
variable x in class Double of type Double has incompatible type;
other members with override errors are: y
val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike

I suspect that this error is due to the way Scala implements vars - as a private field and a getter/setter pair of methods. Is what I try to achieve doable? Is there another way to define the java class fields in the trait and then refer to them inside the concrete trait methods?

Thanks in advance Jack Dimas

like image 666
ideathbird Avatar asked Feb 07 '12 00:02

ideathbird


1 Answers

Yes, it is doable but instead of trying to access the private fields of the classes you want to mix in with (which is most likely a bad idea anyway), you would want to declare the self-type of RectangleLike to be java.awt.geom.RectangularShape so that you can use your trait with e.g. Ellipse2D.Double just as well as with Rectangle2D.Double.

Here is how it works:

trait RectangleLike {
  self: java.awt.geom.RectangularShape =>

  def translate(dx: Double, dy: Double) {
    setFrame(getX + dx, getY + dy, getX + getWidth, getY + getHeight)
  }
}

object Test {
  val foo = new java.awt.geom.Ellipse2D.Double with RectangleLike
}

By saying self: java.awt.geom.RectangularShape => you declare the self-type of your trait which enables you to access all corresponding methods like the necessary getters and setters, allows for using your trait with all descendants of RectangularShape, and also "restricts" your trait so that it can only be used as a mixin to classes which themselves are subtypes of RectangularShape.

The alternative to the above scenario is using a so-called view of your RectangularShape which is a common paradigm as well. For this, you would e.g. declare a class

class RichRectangularShape(shape: java.awt.geom.RectangularShape) {
  def translate(dx: Double, dy: Double) {
    shape.setFrame(shape.getX + dx, shape.getY + dy,
                   shape.getX + shape.getWidth, shape.getY + shape.getHeight)
  }
}

Scala has a way of implicitly viewing an object of one type as an object of another type. If you happen to call a method on a object which is not declared in its corresponding type, the compiler trys to find a type that provides this method and in particular also trys to find an implicit conversion so that your original object can be viewed as an instance of the latter type. For this to work, you would typically declare the companion object of RichRectangularShape as something like this:

object RichRectangularShape {
  implicit def mkRRS(shape: java.awt.geom.RectangularShape): RichRectangularShape =
    new RichRectangularShape(shape)
}

Then:

scala> import RichRectangularShape._
import RichRectangularShape._

scala> val foo = new java.awt.geom.Ellipse2D.Double
foo: java.awt.geom.Ellipse2D.Double = java.awt.geom.Ellipse2D$Double@0

scala> foo.translate(5,2)

scala> foo.getX
res1: Double = 5.0

scala> foo.getY
res2: Double = 2.0

scala> :t foo
java.awt.geom.Ellipse2D.Double
like image 122
fotNelton Avatar answered Sep 21 '22 18:09

fotNelton