61A Lecture 18Monday, October 10Monday, October 10, 2011Generic Functions, ContinuedA function might want to operate on multiple data typesLast time: •Polymorphic functions using message passing•Interfaces: collections of messages with a meaning for each•Two interchangeable implementations of complex numbersToday:•An arithmetic system over related types•Type dispatching instead of message passing•Data-directed programming•Type coercionWhat's different? Today's generic functions apply to multiple arguments that don't share a common interface2Monday, October 10, 2011The Independence of Data TypesData abstraction and class definitions keep types separateSome operations need to cross type boundaries3add_rat mul_ratRational numbers as numerators & denominatorsadd_complex mul_complexComplex numbers astwo-dimensional vectorsHow do we add a complex number and a rational number together?There are many different techniques for doing this!Monday, October 10, 2011Rational Numbers, Now with ClassesRational numbers represented as a numerator and denominator4 class Rational(object): def __init__(self, numer, denom): g = gcd(numer, denom) self.numer = numer // g self.denom = denom // g def __repr__(self): return 'Rational({0}, {1})'.format(self.numer, self.denom) def add_rational(x, y): nx, dx = x.numer, x.denom ny, dy = y.numer, y.denom return Rational(nx * dy + ny * dx, dx * dy) def mul_rational(x, y): return Rational(x.numer * y.numer, x.denom * y.denom)DemoGreatest common divisorNow with property methods, these might call functionsMonday, October 10, 2011Complex Numbers: the Rectangular Representation5 class ComplexRI(object): def __init__(self, real, imag): self.real = real self.imag = imag @property def magnitude(self): return (self.real ** 2 + self.imag ** 2) ** 0.5 @property def angle(self): return atan2(self.imag, self.real) def __repr__(self): return 'ComplexRI({0}, {1})'.format(self.real, self.imag) def add_complex(z1, z2): return ComplexRI(z1.real + z2.real, z1.imag + z2.imag)Might be either ComplexMA or ComplexRI instancesDemoMonday, October 10, 2011Type DispatchingDefine a different function for each possible combination of types for which an operation (e.g., addition) is valid6 def iscomplex(z): return type(z) in (ComplexRI, ComplexMA) def isrational(z): return type(z) == Rational def add_complex_and_rational(z, r): return ComplexRI(z.real + r.numer/r.denom, z.imag) def add_by_type_dispatching(z1, z2): """Add z1 and z2, which may be complex or rational.""" if iscomplex(z1) and iscomplex(z2): return add_complex(z1, z2) elif iscomplex(z1) and isrational(z2): return add_complex_and_rational(z1, z2) elif isrational(z1) and iscomplex(z2): return add_complex_and_rational(z2, z1) else: add_rational(z1, z2)Converted to areal number (float)DemoMonday, October 10, 2011Tag-Based Type DispatchingIdea: Use dictionaries to dispatch on type7 def type_tag(x): return type_tag.tags[type(x)] type_tag.tags = {ComplexRI: 'com', ComplexMA: 'com', Rational: 'rat'} def add(z1, z2): types = (type_tag(z1), type_tag(z2)) return add.implementations[types](z1, z2) add.implementations = {} add.implementations[('com', 'com')] = add_complex add.implementations[('rat', 'rat')] = add_rational add.implementations[('com', 'rat')] = add_complex_and_rational add.implementations[('rat', 'com')] = add_rational_and_complexlambda r, z: add_complex_and_rational(z, r)Declares that ComplexRI and ComplexMA should be treated uniformlyMonday, October 10, 2011m · (m − 1) · n4 · (4 − 1) · 4 = 48Type Dispatching AnalysisMinimal violation of abstraction barriers: we define cross-type functions as necessary, but use abstract data typesExtensible: Any new numeric type can "install" itself into the existing system by adding new entries to various dictionaries8Question: How many cross-type implementations are required to support m types and n operations? def add(z1, z2): types = (type_tag(z1), type_tag(z2)) return add.implementations[types](z1, z2)integer, rational, real, complexadd, subtract, multiply, divideMonday, October 10, 2011Type Dispatching AnalysisMinimal violation of abstraction barriers: we define cross-type functions as necessary, but use abstract data typesExtensible: Any new numeric type can "install" itself into the existing system by adding new entries to various dictionaries9Arg 1Arg 2AddMultiplyComplexComplexRationalRationalComplexRationalRationalComplexMessage PassingType DispatchingMonday, October 10, 2011Data-Directed ProgrammingThere's nothing addition-specific about add_by_typeIdea: One dispatch function for (operator, types) pairs10 def apply(operator_name, x, y): tags = (type_tag(x), type_tag(y)) key = (operator_name, tags) return apply.implementations[key](x, y)DemoMonday, October 10, 2011CoercionIdea: Some types can be converted into other typesTakes advantage of structure in the type system11>>> def rational_to_complex(x): return ComplexRI(x.numer/x.denom, 0)>>> coercions = {('rat', 'com'): rational_to_complex}Question: Can any numeric type be coerced into any other?Question: Have we been repeating ourselves with data-directed programming?Monday, October 10, 2011Applying Operators with Coercion1. Attempt to coerce arguments into values of the same type2. Apply type-specific (not cross-type) operations12 def coerce_apply(operator_name, x, y): tx, ty = type_tag(x), type_tag(y) if tx != ty: if (tx, ty) in coercions: tx, x = ty, coercions[(tx, ty)](x) elif (ty, tx) in coercions: ty, y = tx, coercions[(ty, tx)](y) else: return 'No coercion possible.' key = (operator_name, tx) return coerce_apply.implementations[key](x, y)DemoMonday, October 10, 2011Coercion AnalysisMinimal violation of abstraction barriers: we define cross-type coercion as necessary, but use abstract data typesRequires that all types can be coerced into a common typeMore sharing: All operators use the same coercion scheme13Arg 1Arg 2AddMultiplyComplexComplexRationalRationalComplexRationalRationalComplexFromToCoerceComplexRationalRationalComplexTypeAddMultiplyComplexRationalMonday, October 10,
View Full Document