# CSE341 Fall 2011 # Lecture 23: Extensibility in FP and OOP (see also SML and Java code) # stage 1: simple expressions: # imagine we first implement a simple interpreter for Int, Negate and Add # * each variant of data is a class (can have a common superclass to # emphasize they all "are Exps" and potentially to share helper methods) # * 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 a type-checker would give us a to-do list # (see Java code and adding an abstract method to the superclass) # stage 3: binary operations: # * add Strngs and Rationals and meaning of Add for various combinations # * to avoid non-OOP "is_a?" 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 class Exp # could put default implementations or helper methods here end class Int < Exp attr_reader :i def initialize i @i = i end def eval # no argument because no environment self end def toString @i.to_s end def hasZero i==0 end # stage 2: new operation def noNegConstants if i < 0 Negate.new(Int.new(-i)) else self end end # stage 3: double-dispatch for adding values def add_values v # first dispatch v.addInt self end def addInt v # second dispatch: other is Int Int.new(v.i + i) end def addString v # second dispatch: other is Strng (notice order flipped) Strng.new(v.s + i.to_s) end def addRational v # second dispatch: other is Rational Rational.new(v.i+v.j*i,v.j) end end class Negate < Exp attr_reader :e def initialize e @e = e end def eval Int.new(-e.eval.i) # error if e.eval not an Int end def toString "-(" + e.toString + ")" end def hasZero e.hasZero end # stage 2: new operation def noNegConstants Negate.new(e.noNegConstants) end end class Add < Exp attr_reader :e1, :e2 def initialize(e1,e2) @e1 = e1 @e2 = e2 end # stage 1 and stage 2 version of eval # def eval # Int.new(e1.eval.i + e2.eval.i) # error if e1.eval or e2.eval not an Int # end # stage 3 version def eval e1.eval.add_values e2.eval # start off the double-dispatch end def toString "(" + e1.toString + " + " + e2.toString + ")" end def hasZero e1.hasZero || e2.hasZero end # stage 2: new operation def noNegConstants Add.new(e1.noNegConstants,e2.noNegConstants) end end # stage 2: new Mult class easy to add by itself class Mult < Exp attr_reader :e1, :e2 def initialize(e1,e2) @e1 = e1 @e2 = e2 end def eval Int.new(e1.eval.i * e2.eval.i) # error if e1.eval or e2.eval not an Int end def toString "(" + e1.toString + " * " + e2.toString + ")" end def hasZero e1.hasZero || e2.hasZero end # stage 2: new operation def noNegConstants Mult.new(e1.noNegConstants,e2.noNegConstants) end end # stage 3: new Strng and Rational classes (not named String b/c built-in) class Strng < Exp attr_reader :s def initialize s @s = s end def eval self end def toString s end def hasZero false end # stage 2: new operation def noNegConstants self end # stage 3: double-dispatch for adding values def add_values v # first dispatch v.addString self end def addInt v # second dispatch: other is Int (notice order is flipped) Strng.new(v.i.to_s + s) end def addString v # second dispatch: other is Strng (notice order flipped) Strng.new(v.s + s) end def addRational v # second dispatch: other is Strng (notice order flipped) Strng.new(v.i.to_s + "/" + v.j.to_s + s) end end class Rational < Exp attr_reader :i, :j def initialize(i,j) @i = i @j = j end def eval self end def toString i.to_s + "/" + j.to_s end def hasZero i==0 end def noNegConstants if i < 0 && j < 0 Rational.new(-i,-j) elsif j < 0 Negate.new(Rational.new(i,-j)) elsif i < 0 Negate.new(Rational.new(-i,j)) else self end end def add_values v # first dispatch v.addRational self end def addInt v # second dispatch: reuse computation of commutative operation v.addRational self end def addString v # second dispatch: other is Strng (notice order flipped) Strng.new(v.s + i.to_s + "/" + j.to_s) end def addRational v # second dispatch: other is Strng (notice order flipped) a,b,c,d = i,j,v.i,v.j Rational.new(a*d+b*c,b*d) end end