// CSE341 Fall 2011 // Lecture 23: Extensibility in FP and OOP (see also SML and Ruby code) // stage 1: simple expressions: // imagine we first implement a simple interpreter for Int, Negate and Add // * each variant of data is a subclass of the abstract Exp class // for type-checking purposes -- they could also share helper methods // in Exp, though that was unnecessary here // * use mutation-free objects with getter methods // * each operation is a method that each class defines // stage 2: simple extensions: // * adding a new variant (e.g., Mult) does not affect existing code // * adding a new operations (e.g., noNegConstants) does not affect // existing code, but the type-checker gives us a to-do list once we // add the abstract method to Exp // stage 3: binary operations: // * add Strngs and Rationals and meaning of Add for various combinations // * to avoid non-OOP "instanceof" approach, introduction of the // /double-dispatch/ idiom to code up all possible pairs of variants // * (in practice we would surely support negation and multiplicaiton on // rationals, but for simplicity we let eval raise a run-time error instead) abstract class Exp { abstract Value eval(); // no argument because no environment abstract String toStrng(); // renaming b/c toString in Object is public abstract boolean hasZero(); // stage 2: new operation abstract Exp noNegConstants(); } abstract class Value extends Exp { // stage 3 abstract Value add_values(Value other); // first dispatch abstract Value addInt(Int other); // second dispatch abstract Value addString(Strng other); // second dispatch abstract Value addRational(Rational other); // second dispatch } class Int extends Value { public int i; Int(int i) { this.i = i; } Value eval() { return this; } String toStrng() { return "" + i; } boolean hasZero() { return i==0; } // stage 2: new operation Exp noNegConstants() { if(i < 0) return new Negate(new Int(-i)); else return this; } // stage 3: double-dispatch for adding values Value add_values(Value other) { return other.addInt(this); } Value addInt(Int other) { return new Int(other.i + i); } Value addString(Strng other) { return new Strng(other.s + i); } Value addRational(Rational other) { return new Rational(other.i+other.j*i,other.j); } } class Negate extends Exp { public Exp e; Negate(Exp e) { this.e = e; } Value eval() { // we downcase from Exp to Int, which will raise a run-time error // if the subexpression does not evaluate to an Int return new Int(- ((Int)(e.eval())).i); } String toStrng() { return "-(" + e.toStrng() + ")"; } boolean hasZero() { return e.hasZero(); } // stage 2: new operation Exp noNegConstants() { return new Negate(e.noNegConstants()); } } class Add extends Exp { Exp e1; Exp e2; Add(Exp e1, Exp e2) { this.e1 = e1; this.e2 = e2; } // stage 1 and stage 2 version of eval Value eval_stage1() { // we downcase from Exp to Int, which will raise a run-time error // if either subexpression does not evaluate to an Int return new Int(((Int)(e1.eval())).i + ((Int)(e2.eval())).i); } // stage 3 version Value eval() { return e1.eval().add_values(e2.eval()); } String toStrng() { return "(" + e1.toStrng() + " + " + e2.toStrng() + ")"; } boolean hasZero() { return e1.hasZero() || e2.hasZero(); } // stage 2: new operation Exp noNegConstants() { return new Add(e1.noNegConstants(), e2.noNegConstants()); } } // stage 2: new Mult class easy to add by itself class Mult extends Exp { Exp e1; Exp e2; Mult(Exp e1, Exp e2) { this.e1 = e1; this.e2 = e2; } Value eval() { // we downcase from Exp to Int, which will raise a run-time error // if either subexpression does not evaluate to an Int return new Int(((Int)(e1.eval())).i * ((Int)(e2.eval())).i); } String toStrng() { return "(" + e1.toStrng() + " * " + e2.toStrng() + ")"; } boolean hasZero() { return e1.hasZero() || e2.hasZero(); } // stage 2: new operation Exp noNegConstants() { return new Mult(e1.noNegConstants(), e2.noNegConstants()); } } class Strng extends Value { public String s; Strng(String s) { this.s = s; } Value eval() { return this; } String toStrng() { return s; } boolean hasZero() { return false; } // stage 2: new operation Exp noNegConstants() { return this; } // stage 3: double-dispatch for adding values Value add_values(Value other) { return other.addString(this); } Value addInt(Int other) { return new Strng("" + other.i + s); } Value addString(Strng other) { return new Strng(other.s + s); } Value addRational(Rational other) { return new Strng("" + other.i + "/" + other.j + s); } } class Rational extends Value { int i; int j; Rational(int i, int j) { this.i = i; this.j = j; } Value eval() { return this; } String toStrng() { return "" + i + "/" + j; } boolean hasZero() { return i==0; } // stage 2: new operation Exp noNegConstants() { if(i < 0 && j < 0) return new Rational(-i,-j); else if(j < 0) return new Negate(new Rational(i,-j)); else if(i < 0) return new Negate(new Rational(-i,j)); else return this; } // stage 3 Value add_values(Value other) { return other.addRational(this); } Value addInt(Int other) { // reuse computation of commutative operation return other.addRational(this); } Value addString(Strng other) { return new Strng(other.s + i + "/" + j); } Value addRational(Rational other) { int a = i; int b = j; int c = other.i; int d = other.j; return new Rational(a*d+b*c,b*d); } }