Last active
March 27, 2024 03:49
-
-
Save contrun/576c65b7889b3bc81db97284e4edc7c1 to your computer and use it in GitHub Desktop.
A failed attempt to port Wadler's original Generic Java solution to expression problem to modern Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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<This extends Module1F<This>> with | |
// final class Module1 extends Module1F<Module1> {} | |
// 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<This extends Module1F<This>> { | |
// 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<R> { | |
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<R> is not a static type, | |
// and accessing This.Visitor<R> from a static context is not allowed. | |
public <R> R visit(This.Visitor<R> 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> R visit(Visitor<R> v) { | |
return v.forTriangle(a_, b_, c_); | |
} | |
} | |
// Implement an operator based on visitor pattern. | |
class Perimeter implements Visitor<Double> { | |
public Double forTriangle(Double a, Double b, Double c) { | |
return a + b + c; | |
} | |
} | |
} | |
final class Module1 extends Module1F<Module1> { | |
} | |
class Module2F<This extends Module2F<This>> extends Module1F<This> { | |
interface Visitor<R> extends Module1F.Visitor<R> { | |
public R forSquare(Double x); | |
} | |
class Square implements PlaneShape { | |
protected final Double x_; | |
public Square(Double x) { | |
x_ = x; | |
} | |
public <R> R visit(Visitor<R> v) { | |
return v.forSquare(x_); | |
} | |
} | |
class Perimeter extends Module1F<This>.Perimeter implements Visitor<Double> { | |
public Double forSquare(Double x) { | |
return 4 * x; | |
} | |
} | |
class Area implements Visitor<Double> { | |
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<Module2> { | |
} | |
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())); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment