msoucy.me

Code, games, and sarcasm
Matt Soucy

Intersecting Interfaces in Java

(modified ) in code by Matt Soucy - Comments

During the off-season for FIRST Robotics, I like to examine the code that was produced and try to find ways to improve. Sometimes this involves looking at other teams' code. Often it involves experimenting with the current code, trying to find new patterns and techniques.

One of the areas of the code that I wasn't ever really fond of, is the Robot Map. For those unfamiliar, RobotMap is essentially a class that stores all the constants that the robot uses. This includes things like which CAN address is associated with each motor. This has some benefits, such as keeping a single list of all the addresses and ports to prevent two subsystems from accidentally reserving the same ones. However, it has some annoying drawbacks:

One of the summer's experiments, therefore, was to try to find a way to reduce those complaints. A potential solution was to not use constants at all. RobotMap would become an interface, providing methods that return the particular instances. There would then be new subclasses for each particular implementation (practice robot vs. competition robot).

Example:

interface RobotMap {
    public SpeedController getLeftDrive();
    public SpeedController getRightDrive();
}

class Maverick implements RobotMap {
    // SpeedControllerGroup implements SpeedController as well as Sendable
    SpeedControllerGroup leftGroup = new SpeedControllerGroup(new WPI_TalonSRX(8), new WPI_TalonSRX(9));
    SpeedControllerGroup rightGroup = new SpeedControllerGroup(new WPI_TalonSRX(4), new WPI_TalonSRX(5));

    @Override
    public SpeedController getLeftDrive() {
        return leftGroup;
    }

    @Override
    public SpeedController getRightDrive() {
        return rightGroup;
    }
}

This seemed like a solution that we liked, though we quickly ran into a problem. Within subsystem classes, there's a method called addChild. This method takes an object that implements Sendable, and makes it a part of that subsystem on the dashboard. In the implementation that we have above, getLeftDrive and getRightDrive both return SpeedController objects. SpeedController is completely separate from Sendable, though entirely coincidentally everything that implements SpeedController also implements Sendable. This is problematic, as by returning and storing a SpeedController we lose the ability to add it as a child!

Dotty, a compiler that's designed to be the future of Scala, adds a new concept to its type system - Intersection Types. From their example on them:

trait Resettable {
  def reset(): this.type
}
trait Growable[T] {
  def add(x: T): this.type
}
def f(x: Resettable & Growable[String]) = {
  x.reset()
  x.add("first")
}

In the function f, the variable x is required to implement both of the provided interfaces. This seemed like a perfect solution to my problem, but of course I ran into a roadblock right away - Java doesn't support this functionality.

Or so I thought.

As it turns out, Java supports them in a few select situations - the one I care most about is in their generic constraints. I decided that if I couldn't have intersection typed variables, I'd make my own.

Step one was to make a "combined" interface:

interface A {
    public void doA();
}
interface B {
    public void doB();
}
interface AB extends A,B {
}

Step two was to provide a way to wrap something that's both an A and a B, into an AB.

interface AB extends A,B {
    // This only works with Java 8+
    static <T extends A & B> AB wrap(T t) {
        return null;
    }
}

Step three was to use an anonymous class within the wrapper function, to dispatch calls from both interfaces to the wrapped object.

interface AB extends A,B {
    // This only works with Java 8+
    static <T extends A & B> AB wrap(T t) {
        return new AB() {
            @Override
            public void doA() {
                t.doA();
            }
            @Override
            public void doB() {
                t.doB();
            }
        };
    }
}

This got slightly tedious with larger interfaces, such as SpeedController and Sendable, but has the nice benefit of not having the wrapper contain any state itself.

Finally, I used my new SendableSpeedController in places where I needed both sets of functionality:

interface RobotMap {
    public SendableSpeedController getLeftDrive();
    public SendableSpeedController getRightDrive();
}

class Maverick implements RobotMap {
    // SpeedControllerGroup implements SpeedController as well as Sendable
    SpeedControllerGroup leftGroup = new SpeedControllerGroup(new WPI_TalonSRX(8), new WPI_TalonSRX(9));
    SpeedControllerGroup rightGroup = new SpeedControllerGroup(new WPI_TalonSRX(4), new WPI_TalonSRX(5));

    @Override
    public SendableSpeedController getLeftDrive() {
        return SendableSpeedController.wrap(leftGroup);
    }

    @Override
    public SendableSpeedController getRightDrive() {
        return SendableSpeedController.wrap(rightGroup);
    }
}