// Wadler's the expression problem email https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt // introduced the term expression problem and suggested a solution in Generic Java. The code within the email // actually does not work. Below is Wadler's original words. // 2. A caveat with regard to inner interfaces // In GJ as it is currently implemented, type parameters do not scope // over static members, and further, a type parameter may be indexed only // by non-static classes or interfaces defined in the bound. And in Java // as it is currently defined, all inner interfaces are taken as static, // whether declared so are not. This makes the mechanism for indexing // type variables by inner classes useless for interfaces, greatly // reducing its utility. In particular, it invalidates the solution just // presented, which depends on Visitor being an interface. // Fortunately, it looks possible to relax either the GJ or the Java // constraint. So far as I can see, the only difference in making an // interface non-static is that it can now include non-static inner // classes; so the change would not render invalid any existing Java // programs. But this point requires further study. Also, I should note // that since the changes have not been implemented yet, I have not // actually run the proposed solution. (I did translate from GJ to Java // by hand, and run that.) // I tried to port the original code into modern java (with a different example). // But I found I was unable to refer to the sibling trait within the same class. // I tried to use `This.Visitor` in interface `PlaneShape`, which failed with error // Cannot make a static reference to the non-static type This Java (536871434) // This seemed to be essentially Wadler's caveat. // I don't know if we can work around this 20 years later. // class Module1F> with // final class Module1 extends Module1F {} // can be used to make This type variable in Module1F refer to exactly the // same class (instead of possibly subclasses). // Quoting The Expression Problem by Philip Wadler // This use of `This' is the standard trick to provide accurate static typing in the prescence of subtypes (sometimes called MyType or ThisType). // See also Is there a way to refer to the current type with a type variable? // https://stackoverflow.com/questions/7354740/is-there-a-way-to-refeclass class Module1F> { // A Visitor trait that is bounded by the trait Module1F. // We may think this as a Visitor specialized to the PlaneShape defined below. // Quoting The Expression Problem by Philip Wadler // The key trick here is the use of This.Exp and This.Visitor, via the // mechanism described in `Do parametric types beat virtual types?'. // Recall that mechanism allows a type variable to be indexed by any // inner class defined in the variable's bound. interface Visitor { public R forTriangle(Double a, Double b, Double c); } // The data type we want to extend. interface PlaneShape { // Instead of passing a generic Visitor to this function, we pass a // This.Visitor. // This sibling interface Visitor may use methods specific to some data // variants, // e.g. forTriangle method specific to Triangles here! // There is actually an error in the code below. The error is: // Cannot make a static reference to the non-static type This Java (536871434), // which as far as I know means that This.Visitor is not a static type, // and accessing This.Visitor from a static context is not allowed. public R visit(This.Visitor v); } // A data type variant of the PlaneShape interface. class Triangle implements PlaneShape { protected final Double a_, b_, c_; public Triangle(Double a, Double b, Double c) { a_ = a; b_ = b; c_ = c; } // The public entry point for the visitor, used to run a specific operator for // this data variant. public R visit(Visitor v) { return v.forTriangle(a_, b_, c_); } } // Implement an operator based on visitor pattern. class Perimeter implements Visitor { public Double forTriangle(Double a, Double b, Double c) { return a + b + c; } } } final class Module1 extends Module1F { } class Module2F> extends Module1F { interface Visitor extends Module1F.Visitor { public R forSquare(Double x); } class Square implements PlaneShape { protected final Double x_; public Square(Double x) { x_ = x; } public R visit(Visitor v) { return v.forSquare(x_); } } class Perimeter extends Module1F.Perimeter implements Visitor { public Double forSquare(Double x) { return 4 * x; } } class Area implements Visitor { public Double forTriangle(Double a, Double b, Double c) { Double s = (a + b + c) / 2.0; Double area = Math.sqrt(s * (s - a) * (s - b) * (s - c)); return area; } public Double forSquare(Double x) { return x * x; } } } final class Module2 extends Module2F { } final class Main { static public void main(String[] args) { Module2 m1 = new Module2(); Module2.PlaneShape e1 = m1.new Triangle(3.0, 4.0, 5.0); System.out.println("Perimeter: " + e1.visit(m2.new Perimeter())); Module2 m2 = new Module2(); Module2.PlaneShape e2 = m2.new Square(3.0); System.out.println("Perimeter: " + e2.visit(m2.new Perimeter())); System.out.println("Area: " + e2.visit(m2.new Area())); } }