SLIDE 1 Case Study: The Number Hierarchy
Goal: Develop feel for programming in the large Issues we’ll consider along the way:
– What methods go where?
– Class methods establish invariants – Instance methods must maintain them
– “semi-private” methods for like objects – Double-dispatch when argument form matters.
SLIDE 2
The Number Hierarchy
Object Magnitude Number Fraction Float Integer
SLIDE 3 Instance protocol for Magnitude
= aMagnitude equality (like Magnitudes) < aMagnitude comparison (ditto) > aMagnitude comparison (ditto) <= aMagnitude comparison (ditto) >= aMagnitude comparison (ditto) min: aMagnitude minimum (ditto) max: aMagnitude maximum (ditto) Subclasses: Date, Natural
- Compare Date with Date, Natural w/Natural, . . .
SLIDE 4
Implementation of Magnitude: Reuse
(class Magnitude ; abstract class [subclass-of Object] (method = (x) (self subclassResponsibility)) ; may not inherit = from Object (method < (x) (self subclassResponsibility)) (method > (y) (y < self)) (method <= (x) ((self > x) not)) (method >= (x) ((self < x) not)) (method min: (aMag) ((self < aMag) ifTrue:ifFalse: {self} {aMag})) (method max: (aMag) ((self > aMag) ifTrue:ifFalse: {self} {aMag})) )
SLIDE 5
The Number Hierarchy, Reprise
Object Magnitude Number Fraction Float Integer
SLIDE 6 Instance protocol for Number
negated reciprocal abs absolute value + aNumber addition
subtraction * aNumber multiplication / aNumber division (may answer a Fraction!) isNegative sign check isNonnegative sign check isStrictlyPositive sign check coerce: aNumber class of receiver, value of argument asInteger conversion asFraction conversion asFloat conversion
SLIDE 7 Object-oriented design
Given that class Number inherits from class Magnitude, which of these methods of class Number can be implemented in terms of others? negated * coerce: reciprocal / asInteger abs isNegative asFraction + isNonnegative asFloat
SLIDE 8
Concrete Number Methods
(method - (y) (self + (y negated))) (method abs () ((self isNegative) ifTrue:ifFalse: {(self negated)} {self})) (method / (y) (self * (y reciprocal))) (method isNegative () (self < (self coerce: 0))) (method isNonnegative () (self >= (self coerce: 0))) (method isStrictlyPositive () (self > (self coerce: 0)))
SLIDE 9
Abstract Number Methods
(class Number [subclass-of Magnitude] ; abstract class ;;;;;;; arithmetic (method + (aNumber) (self subclassResponsibility)) (method * (aNumber) (self subclassResponsibility)) (method negated () (self subclassResponsibility)) (method reciprocal () (self subclassResponsibility)) ;;;;;;; coercion (method asInteger () (self subclassResponsibility)) (method asFraction () (self subclassResponsibility)) (method asFloat () (self subclassResponsibility)) (method coerce: (aNumber) (self subclassResponsibility)) )
SLIDE 10
Extended Number Hierarchy
Object Magnitude Natural Number Fraction Float Integer SmallInteger LargeInteger LargePositiveInteger LargeNegativeInteger
SLIDE 11
Example class Fraction: Initialization
(class Fraction [subclass-of Number] [ivars num den] ;; invariants: lowest terms, minus sign on top ;; maintained by divReduce, signReduce (class-method num:den: (a b) ((self new) initNum:den: a b)) (method initNum:den: (a b) ; private (self setNum:den: a b) (self signReduce) (self divReduce)) (method setNum:den: (a b) (set num a) (set den b) self) ; private .. other methods of class Fraction ... )
SLIDE 12 Information revealed to self
Instance variables num and den
- Directly available
- Always and only go with self
Object knows its own representation, invariants, private methods:
(method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce))
SLIDE 13
Information revealed to others
How would you implement coerce:? (Value of argument, representation of receiver)
(method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce)) (method coerce: (aNumber) ...)
Saved: Number protocol includes asFraction!
SLIDE 14 Information revealed to others
How would you implement coerce:?
- Value of argument, rep of receiver
- Challenge: Can’t access rep of argument!
(method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce)) (method coerce: (aNumber) (aNumber asFraction))
Saved: Number protocol includes asFraction!
SLIDE 15
The Number Hierarchy, Reprise
How to implement comparisons on Fractions? Object Magnitude Number Fraction Float Integer
SLIDE 16 Instance protocol for Magnitude, Reprise
How to implement comparisons on Fractions?
= aMagnitude equality (like Magnitudes) < aMagnitude comparison (ditto) > aMagnitude comparison (ditto) <= aMagnitude comparison (ditto) >= aMagnitude comparison (ditto) min: aMagnitude minimum (ditto) max: aMagnitude maximum (ditto) Subclasses: Date, Natural
- Compare Date with Date, Natural w/Natural, . . .
SLIDE 17
Implementing comparison for Fractions
Alas! Cannot see representation of argument How will you know “equal, less or greater”?
SLIDE 18
Implementing comparison for Fractions
Alas! Cannot see representation of argument Protocol says “like with like”? Extend the protocol
(method num () num) ; extension, semi-private (method den () den) ; extension, semi-private (method = (fr) ((num = (fr num)) and: {(den = (fr den))})) (method < (fr) ((num * (fr den)) < ((fr num) * den)))
SLIDE 19
Extended protocol: Multiply two fractions
How will you multiply two fractions?
SLIDE 20
Extending the protocol to multiply fractions
How will you multiply two fractions?
(method * (aFraction) (((Fraction new) setNum:den: (num * (aFraction num)) (den * (aFraction den))) divReduce))
SLIDE 21 Extending open systems
Number protocol: like multiplies with like Goal:
- Large integers and small integers are both Integers
- Messages =, <, +, * ought to mix freely
Constraint: Each object has its own algorithm.
- Small: Use machine-primitive multiplication
- Large: Multiply magnitudes; choose sign
Double dispatch to the rescue!
SLIDE 22 Double dispatch: Algebraic laws
Laws of multiplication:
(:+ n) * (:- m) == :- (n * m) (:+ n) * (:+ m) == :+ (n * m) (:+ n) * small == (:+ n) * (small asLargeInteger)
But! Can’t distinguish forms of argument Solution: “Dispatch laws”
(:+ n) * (:- m) == ((:- m) timesLP: self) (:+ n) * (:+ m) == ((:+ m) timesLP: self) (:+ n) * small == (small timesLP: self)
Argument to timesLP:
- Understands “large positive integer” protocol
SLIDE 23 Double dispatch methods encode operation & protocol
Example messages:
arg answers the large-positive integer protocol receiver should multiply itself by arg
arg answers the small-integer protocol receiver should add itself to arg Message encodes
- Operation to be performed
- Protocol accepted by argument
SLIDE 24 Double dispatch to implement addition
How do you act?
- As small integer, you’re asked to add large positive integer
N to self (aka plusLP: N) Response: ((self asLargeInteger) + N)
- As small integer, you’re asked to add small integer n to
self (aka plusSP: n) Response: (self + n)
- As large positive integer, you’re asked to add large
positive integer N to self (aka plusLP: N) Response: (self + n)
- As large positive integer, you’re asked to add small integer
n to self (aka plusSP: n) Response: (self * (n asLargeInteger))
SLIDE 25 Your turn: Double dispatch to implement multiplication
How do you act?
- As small integer, you’re asked to multiply large positive
integer N by self (aka timesLP: N) Response: ((self asLargeInteger) * N)
- As small integer, you’re asked to multiply small integer n
by self (aka timesSP: n) Response: (self * n)
- As large positive integer, you’re asked to multiply large
positive integer N by self (aka timesLP: N) Response: (self * n)
- As large positive integer, you’re asked to multiply small
integer n by self (aka timesSP: n) Response: (self * (n asLargeInteger))
SLIDE 26 Your turn: Understanding double dispatch
On what class does each method go?
(aNumber addSmallIntegerTo: self))
(anInteger multiplyByLargePositiveInteger: self))
SLIDE 27 Review: Two kinds of knowledge
I can send message to you:
I can inherit from you:
- I know my subclass responsibilities
SLIDE 28 Knowledge of protocol
Three levels:
- I know only your public methods
Example: send select: to any collection
- You are like me: share semi-private methods
Example: send * or + to Fraction
- I must get to know you: double dispatch
Example: send * to + to any integer
SLIDE 29 Extra: Dealing with overflow
New law for multiplication: (self * small) = ((primitive mulWithOverflow self small {((self asLargeInteger) * small)}) value)) Primitive is not a method
- Answers good block or exception block
- Answer is then sent value