CSE 341 : Programming Languages� �Lecture 23
OOP vs. Functional Decomposition
Zach Tatlock
Spring 2015
Breaking things down
This lecture:
2
The expression example
Well-known and compelling example of a common pattern:
Leads to a matrix (2D-grid) of variants and operations
3
| eval | toString | hasZero | … |
Int | | | | |
Add | | | | |
Negate | | | | |
… | | | | |
Standard approach in ML
[See the ML code]
4
| eval | toString | hasZero | … |
Int | | | | |
Add | | | | |
Negate | | | | |
… | | | | |
Standard approach in OOP
[See the Ruby and Java code]
5
| eval | toString | hasZero | … |
Int | | | | |
Add | | | | |
Negate | | | | |
… | | | | |
A big course punchline
6
| eval | toString | hasZero | … |
Int | | | | |
Add | | | | |
Negate | | | | |
… | | | | |
Extensibility
7
| eval | toString | hasZero | noNegConstants |
Int | | | | |
Add | | | | |
Negate | | | | |
Mult | | | | |
8
| eval | toString | hasZero | noNegConstants |
Int | | | | |
Add | | | | |
Negate | | | | |
Mult | | | | |
Extensibility
The other way is possible
Optional:
9
Thoughts on Extensibility
10
Binary operations
11
| eval | toString | hasZero | … |
Int | | | | |
Add | | | | |
Negate | | | | |
… | | | | |
Example
To show the issue:
Now just defining the addition operation is a different 2D grid:
12
| Int | String | Rational |
Int | | | |
String | | | |
Rational | | | |
ML Approach
Addition is different for most Int, String, Rational combinations
Natural approach: pattern-match on the pair of values
13
fun add_values (v1,v2) =
case (v1,v2) of
(Int i, Int j) => Int (i+j)
| (Int i, String s) => String (Int.toString i ^ s)
| (Int i, Rational(j,k)) => Rational (i*k+j,k)
| (Rational _, Int _) => add_values (v2,v1)
| … (* 5 more cases (3*3 total): see the code *)
fun eval e =
case e of
…
| Add(e1,e2) => add_values (eval e1, eval e2)
Example
To show the issue:
Now just defining the addition operation is a different 2D grid:
Worked just fine with functional decomposition -- what about OOP…
14
| Int | String | Rational |
Int | | | |
String | | | |
Rational | | | |
What about OOP?
Starts promising:
15
class Add
…
def eval
e1.eval.add_values e2.eval
end
end
Classes Int, MyString, MyRational then all implement
class Int
…
def add_values v
… # what goes here?
end
end
First try
16
class Int
def add_values v
if v.is_a? Int
Int.new(v.i + i)
elsif v.is_a? MyRational
MyRational.new(v.i+v.j*i,v.j)
else
MyString.new(v.s + i.to_s)
end
end
Another way…
17
Double-dispatch “trick”
So add_values performs “2nd dispatch” to the correct case of 9!
[Definitely see the code]
18
Why showing you this
19
Works in Java too
[See Java code]
20
abstract class Value extends Exp {
abstract Value add_values(Value other);
abstract Value addInt(Int other);
abstract Value addString(Strng other);
abstract Value addRational(Rational other);
}
class Int extends Value { … }
class Strng extends Value { … }
class Rational extends Value { … }
Being Fair
Belittling OOP style for requiring the manual trick of double dispatch is somewhat unfair…
What would work better:
21
Multimethods
General idea:
If dynamic dispatch is essence of OOP, this is more OOP
Downside: Interaction with subclassing can produce situations where there is “no clear winner” for which method to call
22
Ruby: Why not?
Multimethods a bad fit (?) for Ruby because:
23
Java/C#/C++: Why not?
24