Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save contrun/576c65b7889b3bc81db97284e4edc7c1 to your computer and use it in GitHub Desktop.
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
// 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