expression problem
TRANSCRIPT
The Expression Problem
Attila Magyar, 2016Microsec ltd.
Example● Types ● Operations
● Area● Perimeter● ..●
Area Perimeter ??
Square OK OK
Circle OK Ok
??
Goals
● Adding a row (new type)● Adding a column (new operation)
Area Perimeter Draw
Square OK OK
Circle OK Ok
Triangle
Constraints
● Extensibility● Code-level modularization
– no patching of existing code
● Separate compilation– independent deployability
● Static type safety (?)
Basic class-based OO solution
interface Shape { double area(); double perimeter();}
class Square implements Shape { int a;
public Square(int a) { this.a = a; }
public double area() { return a*a; }
public double perimeter() { return 4*a; }}
Basic class-based OO solution
class Circle implements Shape { int r;
public Circle(int r) { this.r = r; }
public double area() { return r*r*PI; }
public double perimeter() { return 2*r*PI; }}
class Triangle implements Shape { … }
Basic class-based OO solution
● Adding a row (new type) → Easy
Area Perimeter Draw
Square OK OK ?
Circle OK Ok ?
Triangle OK OK ?
Basic class-based OO solution
interface Shape { double area(); double perimeter();
void draw(); // New Operation
}
Basic class-based OO solution
● Adding new row (new shape) → Easy● Adding a column (new operation) → Hard
Area Perimeter Draw
Square OK OK FAIL
Circle OK Ok FAIL
Triangle OK OK
Basic procedural solution abstract class Shape { enum Type { SQUARE, CIRCLE } abstract Type type(); }
class Square extends Shape { public final int a; Square(int a) { this.a = a; }
Type type() { return SQUARE; } }
class Circle extends Shape { public final int r; Circle(int r) { this.r = r; }
Type type() { return CIRCLE; } }
static double area(Shape shape) { switch (shape.type()) { case SQUARE: return ((Square)shape).a * ((Square)shape).a; case CIRCLE: return ((Circle)shape).r * ((Circle)shape).r * PI; ... } }
Basic procedural solution
static double perimeter(Shape shape) { switch (shape.type()) { case SQUARE: return 2 * ((Square)shape).a; case CIRCLE: return 2 * ((Circle)shape).r * PI; ... }}
static void draw(Shape shape) { switch (shape.type()) { case SQUARE: drawSquare(shape); case CIRCLE: drawCircle(shape); ... }}
Basic procedural solution
● Adding a column (new operation) → Easy
Area Perimeter Draw
Square OK OK OK
Circle OK Ok Ok
Triangle ? ?
Basic procedural solution
abstract class Shape { enum Type { SQUARE, CIRCLE, TRIANGLE } abstract Type type();}
static double perimeter(Shape shape) { switch (shape.type()) { case SQUARE: return 4 * ((Square)shape).a; case CIRCLE: return 2 * ((Circle)shape).r * PI; case TRIANGLE: … // New Shape ... }}
Basic procedural solution
● Adding new row (new shape) → Hard● Adding a column (new operation) → Easy
Area Perimeter Draw
Square OK OK OK
Circle OK Ok Ok
Triangle FAIL FAIL
OO with „visitor like” double dispatching
interface Visitor { void visitCircle(Circle circle); void visitSquare(Square square);}
class Square { public final int a; public Square(int a) { this.a = a; }
void accept(Visitor visitor) { visitor.visitSquare(this); }}
class Circle { public final int r; public Circle(int r) { this.r = r; }
void accept(Visitor visitor) { visitor.visitCircle(this); }}
class AreaCalculator implements Visitor { public double result;
public void visitCircle(Circle circle) { result = circle.r * circle.r * PI; } public void visitSquare(Square square) { result = 4 * square.a; }}
class PerimeterCalculator implements Visitor { ... }class Artist implements Visitor { ... }
OO with „visitor like” double dispatching
● Adding a column (new operation/visitor) →Easy
● Adding new row (new shape) → Hard
Area Perimeter Draw
Square OK OK OK
Circle OK Ok Ok
Triangle FAIL FAIL
Open classes
class Square { def a def area() { a * a } def perimeter() { 4 * a}}
class Circle { def r def area() { r * r * PI } def perimeter() { 2 * r * PI }}
Open classes
Circle.metaClass.draw = { println "Drawing circle: $delegate"}
Square.metaClass.draw = { println "Drawing square: $delegate"}
def circle = new Circle(r: 12)circle.draw()
def square = new Square(a: 5)square.draw()
Multi-methods
void m(SomeType object) {
// Single dispatch
object1.method(object2);}
Multi-methods
void m(SomeType object) {
// Single dispatch
object1.method(object2);}
Receiver
Multi-methods
interface Physical { void collideWith(Physical object); }
class Asteroid implements Physical { public void collideWith(Physical object) { // .. } }
class SpaceShip implements Physical { public void collideWith(Physical object) { // .. } }
spaceShip.collideWith(asteroid); // same action spaceShip.collideWith(spaceShip2); // same action
Multimethods(defmulti collide (fn [this that] [(:type this) (:type that)]))
(defmethod collide [:SpaceShip :Asteroid ] [a b] "ship crashed by asteroid")(defmethod collide [:Asteroid :SpaceShip] [a b] "ship crashed by asteroid")(defmethod collide [:SpaceShip :SpaceShip] [a b] "ship ship encounter")(defmethod collide [:Asteroid :Asteroid ] [a b] "asteroid hit other asteroid")
(def ship1 {:type :SpaceShip :x 12 :y 46 :fuel 12 :rocket 45 :life 4})(def ship2 {:type :SpaceShip :x 24 :y 18 :fuel 62 :rocket 25 :life 7})(def asteroid {:type :Asteroid :x 4 :y 5})
(collide ship1 asteroid)(collide ship1 ship2)
Multimethods
(def square1 {:type :Square :a 5})(def circle1 {:type :Circle :r 4})
(defmulti area (fn [this] [(:type this)]))(defmethod area [:Square] [this] (* (:a this) (:a this)))(defmethod area [:Circle] [this] (* 3.14 (* (:r this) (:r this))))
(defmulti perimeter (fn [this] [(:type this)]))(defmethod perimeter [:Square] [this] (* 4 (:a this)))(defmethod perimeter [:Circle] [this] (* 3.14 (* 2 (:r this))))
(area square1)(perimeter square1)
(area circle1)(perimeter circle1)
Multimethods
; adding new Shape
(def triangle1 {:type :Triangle :a 2 :b 3 :c 4})
(defmethod area [:Triangle] [this] (triangle-area this))(defmethod perimeter [:Triangle] [this] (triangle-perimeter this))
; adding new operation
(defmulti draw (fn [this] [(:type this)]))
(defmethod draw [:Square] [this] (draw-square))(defmethod draw [:Circle] [this] (draw-circle))(defmethod draw [:Triangle] [this] (draw-triangle))
Further solutions
● Clojure protocols● https://vimeo.com/11236603
● CLOS generic functionshttps://en.wikipedia.org/wiki/Common_Lisp_Object_System
● Type classes● https://en.wikipedia.org/wiki/Type_class
References
● C9 Lectures: Dr. Ralf Lämmel - Advanced Functional Programming - The Expression Problem
● https://channel9.msdn.com/shows/Going+Deep/C9-Lectures-Dr-Ralf-Laemmel-Advanced-Functional-Programming-The-Expression-Problem/
● The Expression Problem, Philip Wadler, 12 November 1998
● http://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt
● Clojure's Solutions to the Expression Problem
● https://www.infoq.com/presentations/Clojure-Expression-Problem
● Clojure 1.2 Protocols, Stuart Halloway
● https://vimeo.com/11236603