This morning I posted to the Math Forum about how an algebra student, armed with Python, might explore 'composition of functions' using operator overloading.
Wrap ordinary function definitions in the Function class below, and you still have __call__ (to get range outputs from domain inputs), plus you have the ability to write h = f * g where f and g and Function objects, and h is a new function object such that h(x) == f(g(x)) for all x. An important aspect of algebra is polymorphism around * and + (multiplication and addition). These become known in terms of their field, ring and group properties, somewhat obscuring their origins in basic arithmetic. We multiply matrices and polynomials, quaternions and permutations. Python's operator overloading makes it easier to see what's going on: we're putting the definition of __mul__ under the programmer-mathematician's control One of the most primitive of all functions is the mapping of letters to letters, e.g. A->R, B->Q, C->C, D->Z... according to whatever random shuffle. The composition of P1 and P2 then becomes the result of mapping A to R per P2, then R to whatever per P1 (say Z), such that A->Z is the result of P1 * P2. The notions of inverse and identity mapping are well defined. Group properties may be explored (the beginning of abstract algebra). Notice that my algebraic functions below (a quadratic and a linear) need to have their inverses specified by the programmer, should it be that negative powering is likely to happen (for example). Some languages attempt to provide this automatically in simple cases, leaving it to the user to supply it otherwise (I'm thinking of J in particular). I'm not trying to be that fancy. Kirby Relevant post to math-teach: http://mathforum.org/kb/plaintext.jspa?messageID=3863728 #===================================== >>> def f(x): return x*x >>> def g(x): return 2*x + 3 >>> class Function(object): def __init__(self, f, invf = None): self.f = f self.invf = invf def __call__(self,x): return self.f(x) def __mul__(self, other): def newf(x): return self.f( other.f(x)) return Function( newf ) def __pow__(self, exp): if exp==1: return self newself = Function(self.f) if exp < 1: newself = Function(self.invf) for i in range(exp-1): newself *= self return newself #--- composition of function objects using __mul__ >>> of = Function(f) >>> og = Function(g) >>> oh = of * og >>> oh(10) 529 >>> oj = og * of >>> oj(10) 203 >>> import math >>> def invf(x): return math.sqrt(x) #--- expanding to integer powers (including negative powers). >>> of = Function(f, invf) >>> newf = of**(-1) >>> h = newf * of >>> h(10) 10.0 >>> h(20) 20.0 _______________________________________________ Edu-sig mailing list [email protected] http://mail.python.org/mailman/listinfo/edu-sig
