Friday 16 January 2015

Scala traits as Java objects

One of the use cases for traits mentioned in Programming in Scala, Odersky et all, Ch. 12.3 is "enriching interfaces".

 With this approach "To enrich an interface using traits, simply define a trait with a small number of abstract methods—the thin part of the trait’s interface—and a potentially large number of concrete methods, all implemented in terms of the abstract methods. Then you can mix the enrichment trait into a class, implement the thin portion of the interface, and end up with a class that has all of the rich interface available."

The following Scala code is an example:
class Point(val x: Int, val y: Int)

trait Rectangular {
  // abstract methods
  def topLeft: Point
  def bottomRight: Point
  
  
  def left = topLeft.x
  def right = bottomRight.x
  def width = right - left
}

class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { 
  // other methods...
}

With this setup we can write:
    val  rect : Rectangular = new Rectangle(new Point(1, 1), new Point(10, 10))
    println(rect left)  //prints 1
    println(rect right) //prints 10
    println(rect width) //prints 9
We will now have a peek and the resulting decompiled .class files and see how this magic looks like from the Java point of view (1). The Point class is unsurprising:
public class Point {

    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }

    public int y() { return y; }
}

Things become more interesting with Rectangular. The trait results in two Java classes: Rectangular.java and Rectangular$class.java.
public interface Rectangular {
    public abstract Point topLeft();
    public abstract Point bottomRight();
    public abstract int left();
    public abstract int right();
    public abstract int width();
}

public abstract class Rectangular$class {
    public static int left(Rectangular $this) {
        return $this.topLeft().x();
    }

    public static int right(Rectangular $this) {
        return $this.bottomRight().x();
    }

    public static int width(Rectangular $this) {
        return $this.right() - $this.left();
    }

    public static void $init$(Rectangular rectangular) {}
}
Effectively the trait is broken up into two parts - an interface part (Rectangular.java) and an implementing class (Rectangular$class.java) which is not related to Rectangular at all and carries the implemented trait methods as static Java methods.

Finally, the Rectangle class looks like this:
public class Rectangle implements Rectangular {

    private final Point topLeft;
    private final Point bottomRight;

    public Rectangle(Point topLeft, Point bottomRight) {
        this.topLeft = topLeft;
        this.bottomRight = bottomRight;
        Rectangular.class.$init$(this);
    }


    public Point topLeft() { return topLeft; }
    public Point bottomRight() { return bottomRight; }

    // the mixin
    public int left()  { return Rectangular.class.left(this); }
    public int right() { return Rectangular.class.right(this); }
    public int width() { return Rectangular.class.width(this); }
}
where we see that mixin logic is statically delegated to Rectangular.

(1) Compiled with Scala 2.11, decompiled with Jad.

No comments:

Post a Comment