Skip to content

Instantly share code, notes, and snippets.

@friedbrice
Last active March 4, 2021 03:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save friedbrice/1e1a1f889d8ef0c7b7c03d20f646b84e to your computer and use it in GitHub Desktop.
Save friedbrice/1e1a1f889d8ef0c7b7c03d20f646b84e to your computer and use it in GitHub Desktop.
The visitor pattern is essentially the same thing as Church encoding
public final class Visitor {
/** Shape
*
* We wish to define a data type with exactly two variants: Circle and Rectangle.
*
* In particular, we do not want Shape to be open to extension by
* new kinds of variants (as would be the case with an abstract class).
* The reason we do not want the variants of Shape to be open to extension
* is because this will grant us the ability to extend the operations
* that may be performed on Shapes, instead. Each such operation will be
* defined by a particular "ShapeVisitor". "ShapeVisitor" is designed in
* such a way that it exhaustively handles every variant of Shape, which
* would be impossible to do if the variants of Shape were open to extension.
*
* As such, the "Visitor pattern" allows us a design that intentionally
* trades away extensibility in variants of a data structure in exchange for
* extensibility in operations that can be performed on that data structure.
*/
public interface Shape {
public <A> A visit(ShapeVisitor<A> visitor);
}
/** ShapeVisitor
*
* The essense of the visitor pattern is that instead of defining each `Shape`
* operation as a method on the `Shape` class, we encode each operation we
* wish to perform on `Shape`s as a `ShapeVisitor`.
*
* `ShapeVisitor` has one method for each `Shape` variant. The number of
* variants is fixed (as opposed to making `Shape` an abstract class, where
* the number of variants would be extensible) but in exchange for that, the
* operations (encoded as `ShapeVisitor`s as opposed to as methods on `Shape`)
* is extensible.
*/
public interface ShapeVisitor<A> {
A visitCircle(Double x, Double y, Double r);
A visitRectangle(Double x, Double y, Double w, Double h);
}
public static final Shape newCircle(Double x, Double y, Double r) {
return new Shape() {
public <A> A visit(ShapeVisitor<A> visitor) {
return visitor.visitCircle(x, y, r);
}
};
}
public static final Shape newRectangle(Double x, Double y, Double w, Double h) {
return new Shape() {
public <A> A visit(ShapeVisitor<A> visitor) {
return visitor.visitRectangle(x, y, w, h);
}
};
}
public static final Shape exampleCircle =
newCircle(2.0, 1.4, 4.5);
public static final Shape exampleRectangle =
newRectangle(1.3, 3.1, 10.3, 7.7);
/** Defining a `ShapeVisitor` is analogous to pattern matching on the
* variants of `Shape` in a pattern-matching language.
*/
public static final ShapeVisitor<Double> area =
new ShapeVisitor<Double>() {
public Double visitCircle(Double x, Double y, Double r) {
return 2 * Math.PI * Math.pow(r, 2);
}
public Double visitRectangle(Double x, Double y, Double w, Double h) {
return w * h;
}
};
/** We can, and even downstream clients can, define new operations on all
* of `Shape`s variants by creating new `ShapeVisitor`s.
*/
public static final ShapeVisitor<Shape> translate(Double dx, Double dy) {
return new ShapeVisitor<Shape>() {
public Shape visitCircle(Double x, Double y, Double r) {
return newCircle(x + dx, y + dy, r);
}
public Shape visitRectangle(Double x, Double y, Double w, Double h) {
return newRectangle(x + dx, y + dx, w, h);
}
};
}
public static final ShapeVisitor<String> show =
new ShapeVisitor<String>() {
public String visitCircle(Double x, Double y, Double r) {
return String.format("Circle {x = %f, y = %f, r = %f}", x, y, r);
}
public String visitRectangle(Double x, Double y, Double w, Double h) {
return String.format("Rectangle {x = %f, y = %f, w = %f, h = %f}", x, y, w, h);
}
};
public static final void main(String[] args) {
String circleString =
exampleCircle.visit(translate(3.0, 4.0)).visit(show);
String rectangleString =
exampleRectangle.visit(translate(-4.0, -3.0)).visit(show);
System.out.println(circleString);
System.out.println(rectangleString);
}
}
/** With this improved interface, the connection between the visitor
* pattern and pattern matching is much move obvious.
*/
public final class VisitorImproved {
public interface Shape {
public <A> A match(ShapeCases<A> cases);
}
public interface ShapeCases<A> {
public A caseCircle(Double x, Double y, Double r);
public A caseRectangle(Double x, Double y, Double w, Double h);
}
public static final Shape newCircle(Double x, Double y, Double r) {
return new Shape() {
public <A> A match(ShapeCases<A> cases) {
return cases.caseCircle(x, y, r);
}
};
}
public static final Shape newRectangle(Double x, Double y, Double w, Double h) {
return new Shape() {
public <A> A match(ShapeCases<A> cases) {
return cases.caseRectangle(x, y, w, h);
}
};
}
public static final Shape exampleCircle =
newCircle(2.0, 1.4, 4.5);
public static final Shape exampleRectangle =
newRectangle(1.3, 3.1, 10.3, 7.7);
public static final Double area(Shape shape) {
return shape.match(new ShapeCases<Double>() {
public Double caseCircle(Double x, Double y, Double r) {
return 2 * Math.PI * Math.pow(r, 2);
}
public Double caseRectangle(Double x, Double y, Double w, Double h) {
return w * h;
}
});
}
public static final Shape translate(Shape shape, Double dx, Double dy) {
return shape.match(new ShapeCases<Shape>() {
public Shape caseCircle(Double x, Double y, Double r) {
return newCircle(x + dx, y + dy, r);
}
public Shape caseRectangle(Double x, Double y, Double w, Double h) {
return newRectangle(x + dx, y + dy, w, h);
}
});
}
public static final String show(Shape shape) {
return shape.match(new ShapeCases<String>() {
public String caseCircle(Double x, Double y, Double r) {
return String.format("Circle {x = %f, y = %f, r = %f}", x, y, r);
}
public String caseRectangle(Double x, Double y, Double w, Double h) {
return String.format("Rectangle {x = %f, y = %f, w = %f, h = %f}", x, y, w, h);
}
});
}
public static final void main(String[] args) {
String circleString =
show(translate(exampleCircle, 3.0, 4.0));
String rectangleString =
show(translate(exampleRectangle, -4.0, -3.0));
System.out.println(circleString);
System.out.println(rectangleString);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment