The expression problem ---------------------- This is a classic problem with language design. First described by John Reynolds in 1975. In its current form, described by Phil Wadler in 1998. Goal: define a data type by cases, where - one can add new cases to the data type - add new functions over the data type - without recompiling existing code - with static type safety (i.e., no casts) Example: expressions begin with one case (constants) and one function (eval) add one case: plus add one function: convert to string Think of functions as columns, cases as rows. Want to add both rows and columns. As we'll see: - in OO, columns are fixed (methods in class), can add rows (subclasses) - in functional languages, rows are fixed (cases), but can add columns (functions) OO solution: interface Exp { int eval(); } class Const implements Exp { int x; int eval() { return x; } } class Add implements Exp { Exp left, right; int eval() { return left.eval() + right.eval(); } } Add str. Need to change _all_ existing classes. And recompile. interface Exp { int eval(); String str(); } class Const implements Exp { int x; int eval() { return x; } String str() { return "" + x; } } class Add implements Exp { Exp left, right; int eval() { return left.eval() + right.eval(); } String str() { return left.str() + " + " + right.str(); } } Functional solution: data Exp = Const Int eval :: Exp -> Int eval (Const x) = x Add str: str :: Exp -> String str (Const x) = show x Add another variant. Need to add a case to all the existing functions. And recompile. data Exp = Const Int | Add Exp Exp eval (Const x) = x eval (Add x y) = eval x + eval y str (Const x) = show x str (Add x y) = str x ++ " + " ++ str y Can we simulate the functional solution in OO? Yes. Visitors. interface Visitor { T visit(Const e); T visit(Add e); } interface Exp { T accept(Visitor v); } class Const implements Exp { int x; T accept(Visitor v) { return v.visit(this); } } class Add implements Exp { Exp left, right; T accept(Visitor v) { return v.visit(this); } } Add new functions: class Eval extends Visitor { Integer visit(Const e) { return e.x; } Integer visit(Add e) { return e.left.accept(this) + e.right.accept(this); } } class ToString extends Visitor { String visit(Const e) { return "" + e.x; } String visit(Add e) { return e.left.accept(this) + " + " + e.right.accept(this); } } BUT: if we add a data type variant, need to change all the visitors. We can shift the tradeoff between procedural extensions and data extensions back and forth, but we can't get both at the same time. ----------------------------- Solution using virtual types. Virtual types are nested types with a bound. The bound can be either a subtype bound (<:) or an equality (=). type E <: Exp trait Exp { def eval: Int } In subclasses, derive a more precise bound for E. type E <: ExpWithShow trait ExpWithShow extends Exp { def show: String } See Compiler1.scala for a complete example.