The Expression Problem
- Prof. Dr. Ralf Lämmel
Universität Koblenz-Landau Software Languages Team
The Expression Problem Prof. Dr. Ralf Lmmel Universitt - - PowerPoint PPT Presentation
The Expression Problem Prof. Dr. Ralf Lmmel Universitt Koblenz-Landau Software Languages Team Elevator speech Suppose you have some data variants (say apples and oranges) and you have some operations on such data (say
Universität Koblenz-Landau Software Languages Team
(C) 2010-2015 Ralf Lämmel
Suppose you have some data variants (say “apples” and “oranges”) and you have some
“squeeze”), how would you go about the design of data and operations so that you can hopefully add new data variants and new operations later on?
2
(C) 2010-2015 Ralf Lämmel
– Expressions as in programming languages: – Literals – Addition – …
– Pretty print expressions – Evaluate expressions – …
– Add another expression form
– Add another operation
3
The name goes back to Phil Wadler who defined the expression problem in an email sent to mailing lists and individuals in November 1998.
(C) 2010-2015 Ralf Lämmel
– It is easy to add new data variants. – It is hard to add new operations.
4
(C) 2010-2015 Ralf Lämmel
Data extensibility
5
(C) 2010-2015 Ralf Lämmel
/** * The base class of all expression forms */ public abstract class Expr { /* * Operation for pretty printing */ public abstract String prettyPrint(); /* * Operation for expression evaluation */ public abstract int evaluate(); }
Beware
6
The class rooting all data variants
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "literals" (i.e., constants) */ public class Lit extends Expr { private int info; public int getInfo() { return info; } public void setInfo(int info) { this.info = info; } public String prettyPrint() { return Integer.toString(getInfo()); } public int evaluate() { return getInfo(); } }
Beware
7
A class for a data variant
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "addition" */ public class Add extends Expr { private Expr left, right; public Expr getLeft() { return left; } public void setLeft(Expr left) { this.left = left; } public Expr getRight() { return right; } public void setRight(Expr right) { this.right = right; } public String prettyPrint() { ... } public int evaluate() { return getLeft().evaluate() + getRight().evaluate(); } }
That is, another data variant but with the same operations.
Beware
8
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "negation" */ public class Neg extends Expr { private Expr expr; public Expr getExpr() { return expr; } public void setExpr(Expr expr) { this.expr = expr; } public String prettyPrint() { return "-(" + getExpr().prettyPrint() + ")"; } public int evaluate() { return - getExpr().evaluate(); } }
That is, yet another data variant but, again, with the same operations.
Beware
9
(C) 2010-2015 Ralf Lämmel
– It is easy to add new data variants. – It is hard to add new operations.
– It is easy to add new operations. – It is easy to add new data variants.
10
But beware of programming errors
(C) 2010-2015 Ralf Lämmel
11
(C) 2010-2015 Ralf Lämmel
public abstract class Expr { }
public class Lit extends Expr { private int info; public int getInfo() { return info; } public void setInfo(int info) { this.info = info; } } public class Add extends Expr { private Expr left, right; public Expr getLeft() { return left; } public void setLeft(Expr left) { this.left = left; } public Expr getRight() { return right; } public void setRight(Expr right) { this.right = right; } }
Domain classes are nothing but data capsules.
12
(C) 2010-2015 Ralf Lämmel
public class EvaluatorBase { public int evaluate(Expr e) { if (e instanceof Lit) { Lit l = (Lit)e; return l.getInfo(); } if (e instanceof Add) { Add a = (Add)e; return evaluate(a.getLeft()) + evaluate(a.getRight()); } throw new FallThrouhException(); } }
We simulate pattern matching by instanceof and cast. We anticipate failure of such operations as a means to compose them.
13
(C) 2010-2015 Ralf Lämmel
public class EvaluatorExtension extends EvaluatorBase { public int evaluate(Expr e) { try { return super.evaluate(e); } catch (FallThrouhException x) { if (e instanceof Neg) { Neg n = (Neg)e; return -evaluate(n.getExpr()); } throw new FallThrouhException(); } } }
Recursive calls are properly dispatched through “this” to the extended operation. An extended operation first attempts the basic implementation.
14
(C) 2010-2015 Ralf Lämmel
– It is easy to add new data variants. – It is hard to add new operations.
– It is easy to add new operations. – It is hard to add new data variants.
– It is easy to add new operations. – It is easy to add new data variants.
15
(C) 2010-2015 Ralf Lämmel
Operation extensibility
16
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
interface List {} class Nil implements List {} class Cons implements List {
int head; List tail;
}
Now suppose we need functionality to compute the sum of a list.
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
List l = ...; int sum = 0; // contains the sum after the loop boolean proceed = true; while (proceed) { if (l instanceof Nil) proceed = false; else if (l instanceof Cons) { sum = sum + ((Cons) l).head; // Type cast! l = ((Cons) l).tail; // Type cast! } }
What are the problems here?
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
http://en.wikipedia.org/wiki/File:Codex_Gigas_devil.jpg
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
What are the problems here?
interface List { int sum(); } class Nil implements List { public int sum() { return 0; } } class Cons implements List { int head; List tail; public int sum() { return head + tail.sum(); } }
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
http://en.wikipedia.org/wiki/File:Codex_Gigas_devil.jpg
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Part I: An interface for operations
interface Visitor { void visitNil(Nil x); void visitCons(Cons x); }
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Part II: Classes that accept visitors
interface List { void accept(Visitor v); } class Nil implements List { public void accept(Visitor v) { v.visitNil(this); } } class Cons implements List { int head; List tail; public void accept(Visitor v) { v.visitCons(this); } }
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Part III: A concrete visitor
class SumVisitor implements Visitor { int sum = 0; public void visitNil(Nil x) {} public void visitCons(Cons x){ sum += x.head; x.tail.accept(this); } }
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Demo
SumVisitor sv = new SumVisitor(); l.accept(sv); System.out.println(sv.sum);
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Frequent type casts? Frequent recompilation? Instanceof and type casts
Yes No
Dedicated methods
No Yes
The Visitor pattern
No No
What if we need a new class?
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Two levels of polymorphism
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
http://en.wikipedia.org/wiki/File:VisitorClassDiagram.svg
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
An object structure contains different classes of
these objects depend on their concrete classes. Many unrelated operations need to be performed
The classes defining the object structure rarely change, but you want to define new operations
change often, it is better to define the operations in those classes.)
(C) 2010-2015 Ralf Lämmel
Operation extensibility
33
(C) 2010-2015 Ralf Lämmel
/** * The base class of all expression forms */ public abstract class Expr { /* * Accept a visitor (i.e., apply an operation) */ public abstract <R> R accept(Visitor<R> v); }
Domain operations are no longer hosted in domain classes.
34
(C) 2010-2015 Ralf Lämmel
35
In principle, void visitors are sufficient because one can always (and often has to) aggregate the result through state which is ultimately to be returned through a designated getter.
(C) 2010-2015 Ralf Lämmel
/** * A concrete visitor describe a concrete operation on expressions. * There is one visit method per type in the class hierarchy. */ public abstract class Visitor<R> { public abstract R visit(Lit x); public abstract R visit(Add x); public abstract R visit(Neg x); }
Requires folklore knowledge on design patterns.
36
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "literals" (i.e., constants) */ public class Lit extends Expr { private int info; public int getInfo() { return info; } public void setInfo(int info) { this.info = info; } public <R> R accept(Visitor<R> v) { return v.visit(this); } }
One visitor-enabled data variant.
37
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "addition" */ public class Add extends Expr { private Expr left, right; public Expr getLeft() { return left; } public void setLeft(Expr left) { this.left = left; } public Expr getRight() { return right; } public void setRight(Expr right) { this.right = right; } public <R> R accept(Visitor<R> v) { return v.visit(this); } }
Another visitor-enabled data variant.
Beware
38
(C) 2010-2015 Ralf Lämmel
/** * The expression form of "negation" */ public class Neg extends Expr { private Expr expr; public Expr getExpr() { return expr; } public void setExpr(Expr expr) { this.expr = expr; } public <R> R accept(Visitor<R> v) { return v.visit(this); } }
Beware
Yet another visitor-enabled data variant.
39
(C) 2010-2015 Ralf Lämmel
/** * Operation for pretty printing */ public class PrettyPrinter extends Visitor<String> { public String visit(Lit x) { return Integer.toString(x.getInfo()); } public String visit(Add x) { return x.getLeft().accept(this) + " + " + x.getRight().accept(this); } public String visit(Neg x) { return "- (" + x.getExpr().accept(this) + ")"; } }
Beware
An operation represented as a concrete visitor.
40
(C) 2010-2015 Ralf Lämmel
Beware
Another operation represented as a concrete visitor.
/** * Operation for expression evaluation */ public class Evaluator extends Visitor<Integer> { public Integer visit(Lit x) { return x.getInfo(); } public Integer visit(Add x) { return x.getLeft().accept(this) + x.getRight().accept(this); } public Integer visit(Neg x) { return - x.getExpr().accept(this); } }
41
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
javaComposition javaStatic javaInheritance javaVisitor
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Try to modularize (say, separate concerns). Try to understand extensibility requirements. Avoid modularity exorcism. Take refactoring and testing very seriously. Please don’t print the slides, or print resource-friendly.
(C) 2010-2015 Ralf Lämmel
– ei could be a data extension:
– ei could be an operation extension:
– Any extension can be factored into such primitive ones.
46
(C) 2010-2015 Ralf Lämmel
– Extensions can be modeled as code units. – Existing code units are never edited or cloned.
– The extensions are compiled separately.
– The extension are statically type-checked separately.
47
(C) 2010-2015 Ralf Lämmel
48
(C) 2010-2015 Ralf Lämmel
49
(C) 2010-2015 Ralf Lämmel
50
public abstract partial class Expr { } public partial class Lit : Expr { public int info; } public partial class Add : Expr { public Expr left, right; }
(C) 2010-2015 Ralf Lämmel
51
public abstract partial class Expr { public abstract int Evaluate(); } public partial class Lit { public override int Evaluate() { return info; } } public partial class Add { public override int Evaluate(){ return left.Evaluate() + right.Evaluate(); } }
(C) 2010-2015 Ralf Lämmel
52
public abstract partial class Expr { public abstract string PrettyPrint(); } public partial class Lit { public override string PrettyPrint() { return info.ToString(); } } public partial class Add { public override string PrettyPrint() { return left.PrettyPrint() + " + " + right.PrettyPrint(); } }
(C) 2010-2015 Ralf Lämmel
53
public partial class Neg : Expr { public Expr expr; } public partial class Neg { public override string PrettyPrint() { return "- (" + expr.PrettyPrint() + ")"; } } public partial class Neg { public override int Evaluate() { return -expr.Evaluate(); } }
(C) 2010-2015 Ralf Lämmel
54
(C) 2010-2015 Ralf Lämmel
Operation extensibility
55
(C) 2010-2015 Ralf Lämmel
Algebraic data types of Haskell are closed!
module Data where
data Exp = Lit Int | Add Exp Exp
56
Algebraic data type Constructor Constructor component Constructor 2 constructor components
(C) 2010-2015 Ralf Lämmel
module PrettyPrinter where import Data
prettyPrint :: Exp -> IO () prettyPrint (Lit i) = putStr (show i) prettyPrint (Add l r) = do prettyPrint l; putStr " + "; prettyPrint r
A new module can be designated to each new
57
Operation defined by case discrimination Argumen t type Result type “side effect”
(C) 2010-2015 Ralf Lämmel
Another operation; another module.
module Evaluator where import Data
evaluate :: Exp -> Int evaluate (Lit i) = i evaluate (Add l r) = evaluate l + evaluate r
58
(C) 2010-2015 Ralf Lämmel
59
(C) 2010-2015 Ralf Lämmel
60
(C) 2010-2015 Ralf Lämmel
61
:- multifile prettyPrint/2. prettyPrint(lit(I),S) :- string_to_atom(S,I). prettyPrint(add(L,R),S) :- prettyPrint(L,S1), prettyPrint(R,S2), format(string(S),"~s + ~s", [S1,S2]).
(C) 2010-2015 Ralf Lämmel
62
:- multifile prettyPrint/2. :- multifile evaluate/2. prettyPrint(neg(E),S) :- prettyPrint(E,S1), format(string(S),"- (~s)",[S1]). evaluate(neg(E),I) :- evaluate(E,I1), I is - I1.
(C) 2010-2015 Ralf Lämmel
63
:- multifile evaluate/2. evaluate(lit(I),I). evaluate(add(L,R),I) :- evaluate(L,I1), evaluate(R,I2), I is I1 + I2.
(C) 2010-2015 Ralf Lämmel
64
:- [ 'InitialProgram.pro' , 'OperationExtension.pro' , 'DataExtension.pro' ].
(C) 2010-2015 Ralf Lämmel
65
(C) 2010-2015 Ralf Lämmel
66
(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)
Wanted Data extensibility and Operation extensibility and Soft or strong static type checking and Separate compilation/deployment and Unanticipated extension and Configurability and Composability and Efficiency (“No distributed fat”)