Last active
March 4, 2021 03:37
-
-
Save friedbrice/1e1a1f889d8ef0c7b7c03d20f646b84e to your computer and use it in GitHub Desktop.
The visitor pattern is essentially the same thing as Church encoding
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
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); | |
} | |
} |
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
/** 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