The Expression Problem Prof. Dr. Ralf Lmmel Universitt - - PowerPoint PPT Presentation

the expression problem
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

The Expression Problem

  • Prof. Dr. Ralf Lämmel

Universität Koblenz-Landau Software Languages Team

slide-2
SLIDE 2

(C) 2010-2015 Ralf Lämmel

Elevator speech

Suppose you have some data variants (say “apples” and “oranges”) and you have some

  • perations on such data (say “drink” and

“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

slide-3
SLIDE 3

(C) 2010-2015 Ralf Lämmel

Expression problem: Why the name?

  • Consider such data:

– Expressions as in programming languages: – Literals – Addition – …

  • Consider such operations:

– Pretty print expressions – Evaluate expressions – …

  • Consider such extension scenarios:

– Add another expression form

  • Cases for all existing operations must be added.

– Add another operation

  • Cases for all existing expression forms must be added.

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.

slide-4
SLIDE 4

(C) 2010-2015 Ralf Lämmel

Expression problem: What problem?

  • In basic OO programming:

– It is easy to add new data variants. – It is hard to add new operations.

4

slide-5
SLIDE 5

(C) 2010-2015 Ralf Lämmel

Data extensibility based on simple inheritance

  • class Expr: The base class of all expression forms
  • Virtual methods:
  • method prettyPrint: operation for pretty-printing
  • method evaluate: operation for expression evaluation
  • Subclasses:
  • class Lit: The expression form of "literals"
  • class Add: The expression form of "addition"
  • ...

Data extensibility

5

slide-6
SLIDE 6

(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

  • f code bloat!

6

The class rooting all data variants

slide-7
SLIDE 7

(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

  • f code bloat!

7

A class for a data variant

slide-8
SLIDE 8

(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

  • f code bloat!

8

slide-9
SLIDE 9

(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

  • f code bloat!

9

slide-10
SLIDE 10

(C) 2010-2015 Ralf Lämmel

Expression problem: What problem?

  • In basic OO programming:

– It is easy to add new data variants. – It is hard to add new operations.

  • In OO programming with instance-of / cast:

– It is easy to add new operations. – It is easy to add new data variants.

10

But beware of programming errors

slide-11
SLIDE 11

(C) 2010-2015 Ralf Lämmel

Full (?) extensibility based on instance-of tests and type casts

  • We use instanceof tests and casts to dispatch functionality on data.
  • We use a specific exception and try-catch blocks to extend operations.
  • We encapsulate operations in objects so that extensions are self-aware.
  • This is approach is weakly typed.
  • In particular, there is no guarantee that we have covered all cases.

11

slide-12
SLIDE 12

(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

slide-13
SLIDE 13

(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

slide-14
SLIDE 14

(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

slide-15
SLIDE 15

(C) 2010-2015 Ralf Lämmel

Expression problem: What problem?

  • In basic OO programming:

– It is easy to add new data variants. – It is hard to add new operations.

  • In OO programming with visitors:

– It is easy to add new operations. – It is hard to add new data variants.

  • In OO programming with instance-of / cast:

– It is easy to add new operations. – It is easy to add new data variants.

15

Forthcoming!

slide-16
SLIDE 16

(C) 2010-2015 Ralf Lämmel

Operation extensibility based on visitors PREVIEW

  • class Expr: The base class of all expression forms
  • Subclasses as before.
  • Virtual methods:
  • Accept a visitor (“apply an operation”)
  • Visitors:
  • class PrettyPrinter: operation for pretty-printing
  • class Evaluator: operation for expression evaluation
  • ...

Operation extensibility

16

slide-17
SLIDE 17

The Visitor Pattern

slide-18
SLIDE 18

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

A book recommendation

slide-19
SLIDE 19

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Elevator speech

Q: How can we add functionality to a given (perhaps even closed) class hierarchy such that behavior can vary across concrete classes? A: Represent an operation to be performed on the elements of an

  • bject structure in a class separate

from the given classes.

slide-20
SLIDE 20

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Adding an operation to lists

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.

slide-21
SLIDE 21

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

1st attempt: instance-of and type cast

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?

slide-22
SLIDE 22

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

1st attempt: instance-of and type cast

Casts are evil!

Requirement: static type checking

http://en.wikipedia.org/wiki/File:Codex_Gigas_devil.jpg

slide-23
SLIDE 23

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

2nd attempt: virtual methods

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(); } }

slide-24
SLIDE 24

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

2nd attempt: virtual methods

Patching is evil!

Requirement:

  • peration extensibility

http://en.wikipedia.org/wiki/File:Codex_Gigas_devil.jpg

slide-25
SLIDE 25

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

3rd attempt: the visitor pattern

Part I: An interface for operations

interface Visitor { void visitNil(Nil x); void visitCons(Cons x); }

slide-26
SLIDE 26

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

3rd attempt: the visitor pattern

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); } }

slide-27
SLIDE 27

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

3rd attempt: the visitor pattern

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); } }

slide-28
SLIDE 28

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

3rd attempt: the visitor pattern

Demo

SumVisitor sv = new SumVisitor(); l.accept(sv); System.out.println(sv.sum);

slide-29
SLIDE 29

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Summary so far

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?

slide-30
SLIDE 30

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Summary so far

  • Class hierarchy implements virtual accept.
  • A Visitor object has a visit method for each class.
  • Double dispatch:
  • accept method takes a specific Visitor object.
  • accept redirects to class-specific visit method.

Two levels of polymorphism

slide-31
SLIDE 31

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

The Visitor pattern: generic solution

http://en.wikipedia.org/wiki/File:VisitorClassDiagram.svg

slide-32
SLIDE 32

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Applicability

An object structure contains different classes of

  • bjects with different interfaces and operations on

these objects depend on their concrete classes. Many unrelated operations need to be performed

  • n the objects and you want to keep related
  • perations together.

The classes defining the object structure rarely change, but you want to define new operations

  • ver the structure. (If the object structure classes

change often, it is better to define the operations in those classes.)

slide-33
SLIDE 33

(C) 2010-2015 Ralf Lämmel

Operation extensibility based on visitors

  • class Expr: The base class of all expression forms
  • Subclasses as before.
  • Virtual methods:
  • Accept a visitor (“apply an operation”)
  • Visitors:
  • class PrettyPrinter: operation for pretty-printing
  • class Evaluator: operation for expression evaluation
  • ...

Operation extensibility

33

slide-34
SLIDE 34

(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

slide-35
SLIDE 35

(C) 2010-2015 Ralf Lämmel

35

Intermezzo: visitor types

  • Returning visitors
  • Visit methods have a return type.
  • Void visitors
  • Visit methods are void.

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.

slide-36
SLIDE 36

(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

slide-37
SLIDE 37

(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

slide-38
SLIDE 38

(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

  • f code bloat!

38

slide-39
SLIDE 39

(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

  • f code bloat!

Yet another visitor-enabled data variant.

39

slide-40
SLIDE 40

(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

  • f code bloat!

An operation represented as a concrete visitor.

40

slide-41
SLIDE 41

(C) 2010-2015 Ralf Lämmel

Beware

  • f code bloat!

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

slide-42
SLIDE 42

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

101implementations

javaComposition javaStatic javaInheritance javaVisitor

slide-43
SLIDE 43

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Summary

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.

slide-44
SLIDE 44

Everything beyond this point is “for your information” only.

slide-45
SLIDE 45

The Expression Problem Cont’d

slide-46
SLIDE 46

(C) 2010-2015 Ralf Lämmel

The notion of extensibility

  • The initial system I:
  • Provides operations o1, …, om.
  • Handles data variants d1, …, dn.
  • Consider any number of extensions e1, …, ek:

– ei could be a data extension:

  • Introduce a new data variant.
  • Provide cases for all existing operations.

– ei could be an operation extension:

  • Introduce a new operation.
  • Provide cases for all existing data variants.

– Any extension can be factored into such primitive ones.

  • The fully extended system ek ( … (e1 (I)))

46

slide-47
SLIDE 47

(C) 2010-2015 Ralf Lämmel

More on extensibility

  • Code-level extensibility:

– Extensions can be modeled as code units. – Existing code units are never edited or cloned.

  • Separate compilation:

– The extensions are compiled separately.

  • Statically type-checked extensibility:

– The extension are statically type-checked separately.

47

slide-48
SLIDE 48

(C) 2010-2015 Ralf Lämmel

Beyond Java

  • Functions and pattern matching à la SML, Haskell
  • Multi-file predicates in Prolog
  • Partial classes in C#
  • ...

48

slide-49
SLIDE 49

(C) 2010-2015 Ralf Lämmel

A C# approach to extensibility

  • Use the partial class mechanism of C#
  • A class can be “declared” many times.
  • The list of members is accumulated.

49

slide-50
SLIDE 50

(C) 2010-2015 Ralf Lämmel

The data slice

50

public abstract partial class Expr { } public partial class Lit : Expr { public int info; } public partial class Add : Expr { public Expr left, right; }

slide-51
SLIDE 51

(C) 2010-2015 Ralf Lämmel

The evaluation slice

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(); } }

slide-52
SLIDE 52

(C) 2010-2015 Ralf Lämmel

The pretty-print slice

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(); } }

slide-53
SLIDE 53

(C) 2010-2015 Ralf Lämmel

A data extension

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(); } }

slide-54
SLIDE 54

(C) 2010-2015 Ralf Lämmel

Discussion of the C# approach

  • Code extensibility only
  • All extension need to be compiled together.
  • Slices are not even checked independently.

54

slide-55
SLIDE 55

(C) 2010-2015 Ralf Lämmel

A Haskell approach to operation extensibility

  • data type Expr: The union of all expression forms
  • Public constructors:
  • Lit: The expression form of "literals"
  • Add: The expression form of "addition"
  • Functions defined by pattern matching on Expr:
  • function prettyPrint: operation for pretty-printing
  • function evaluate: operation for expression evaluation
  • ...

Operation extensibility

55

slide-56
SLIDE 56

(C) 2010-2015 Ralf Lämmel

Algebraic data types of Haskell are closed!

module Data where

  • - A data type of expression forms

data Exp = Lit Int | Add Exp Exp

56

Algebraic data type Constructor Constructor component Constructor 2 constructor components

slide-57
SLIDE 57

(C) 2010-2015 Ralf Lämmel

module PrettyPrinter where import Data

  • - Operation for pretty printing

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

  • peration.

57

Operation defined by case discrimination Argumen t type Result type “side effect”

slide-58
SLIDE 58

(C) 2010-2015 Ralf Lämmel

Another operation; another module.

module Evaluator where import Data

  • - Operation for expression evaluation

evaluate :: Exp -> Int evaluate (Lit i) = i evaluate (Add l r) = evaluate l + evaluate r

58

slide-59
SLIDE 59

(C) 2010-2015 Ralf Lämmel

Discussion of the Haskell approach

  • Lack of encapsulation?
  • Only operation extensibility

59

slide-60
SLIDE 60

(C) 2010-2015 Ralf Lämmel

A Prolog approach to extensibility

  • Define operations as predicates.
  • Model data variants through functors.
  • Achieve extensibility through “multi-file” predicates.

60

slide-61
SLIDE 61

(C) 2010-2015 Ralf Lämmel

The initial program

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]).

slide-62
SLIDE 62

(C) 2010-2015 Ralf Lämmel

A data extension

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.

slide-63
SLIDE 63

(C) 2010-2015 Ralf Lämmel

An operation extension

63

:- multifile evaluate/2. evaluate(lit(I),I). evaluate(add(L,R),I) :- evaluate(L,I1), evaluate(R,I2), I is I1 + I2.

slide-64
SLIDE 64

(C) 2010-2015 Ralf Lämmel

Program composition

64

:- [ 'InitialProgram.pro' , 'OperationExtension.pro' , 'DataExtension.pro' ].

slide-65
SLIDE 65

(C) 2010-2015 Ralf Lämmel

Discussion of the Prolog approach

  • Full extensibility
  • No descriptive overhead
  • Lack of static typing
  • Performance penalty

65

slide-66
SLIDE 66

(C) 2010-2015 Ralf Lämmel

Solutions of the expression problem

  • Open classes in more dynamic languages (Smalltalk, etc.)
  • Extensible predicates in Prolog (code-level extensibility only)
  • Java, C# & Co.
  • Clever encodings (Torgersen, ECOOP 2004)
  • AOP-like Open classes (introductions)
  • .NET-like partial classes (code-level extensibility only)
  • Expanders (Warth et al., OOPSLA 2006)
  • JavaGI (Wehr et al., ECOOP 2007)
  • ...

66

slide-67
SLIDE 67

(C) 2010-2015 Prof. Dr. Ralf Lämmel, Universität Koblenz-Landau (where applicable)

Summary on extensibility

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”)