# -*- coding: utf-8 -*-
"""
Created on Thu Feb 11 12:07:59 2016

@author: Kirby Urner

In algebra, we have the idea that functions might compose.  We
have this idea in POSIX too, i.e. the pipeline operator:

$ ls *.py | wc -l

The ls 'function' sends its output to wordcount -lines to say
how many Python modules in a give directory.

Instead of using a | (pipe) to chain or pipeline functions,
lets overload the multiplication and powering operators * and **.

Remember how lambda works to create a piece of functionality,
of one expression?

lambda x: f(g(x)) leaves the argument open i.e. we have not
committed to any special x.

lambda comes in handy below

The unittests would usually be split off into a separate file,
leaving algebra_city.py unencumbered with testing code, but
for now we develop them as one.
"""

import unittest
import types  # <-- to get FunctionType

class Composer:
    """
    Composer swallows a function, which may still be called, by
    calling the instance instead.  Used as a decorator, the Composer
    class enables composition of functions by means of multiplying
    and powering their corresponding Composer instances.
    """

    def __init__(self, func):
        self.func = func     # eat a callable

    def __call__(self, x):
        return self.func(x)  # still a callable

    def __mul__(self, other):
        """
        multiply two Composers i.e. (f * g)(x) == f(g(x))
        g might might a function. OK if f is Composer.
        """
        if isinstance(other, types.FunctionType): # OK if target is a
function
            other = Composer(other)
        if not isinstance(other, Composer): # by this point, other must be
one
            raise TypeError

        return Composer(lambda x: self.func(other.func(x)))  # compose 'em

    def __rmul__(self, other): # in case other is on the left
        """
        multiply two Composers i.e. (f * g)(x) == f(g(x))
        f might might a function. OK if g is Composer.
        """
        if isinstance(other, types.FunctionType): # OK if target is a
function
            other = Composer(other)
        if not isinstance(other, Composer): # by this point, other must be
one
            raise TypeError
        return Composer(lambda x: other.func(self.func(x)))  # compose 'em

    def __pow__(self, exp):
        """
        A function may compose with itself why not?
        """
        # type checking:  we want a non-negative integer
        if not isinstance(exp, int):
            raise TypeError
        if not exp > -1:
            raise ValueError
        me = self
        if exp == 0: # corner case
            return Composer(lambda x: x) # identify function
        elif exp == 1:
            return me                # (f**1) == f
        for _ in range(exp-1):       # e.g. once around loop if exp==2
            me = me * self
        return me

    def __repr__(self):
        return "Composer({})".format(self.func.__name__)

@Composer
def f(x):
    "second powering"
    return x ** 2

@Composer
def g(x):
    "magnifying by 10"
    return x * 10

class TestComposer(unittest.TestCase):

    def test_simple(self):
        x = 5
        self.assertEqual((f*g*g*f*g*f)(x), f(g(g(f(g(f(x)))))), "Not same!")

    def test_function(self):
        def addA(s): # not decorated
            return s + "A"
        @Composer
        def addM(s):
            return s + "M"

        addAM = addM * addA  # Composer times regular function, OK?
        self.assertEqual(addAM("I "), "I AM", "appends A then M")
        addMA = addA * addM  # regular function, times Composer OK?
        self.assertEqual(addMA("HI "), "HI MA", "appends M then A")

    def test_inputs(self):
        self.assertRaises(TypeError, f.__pow__, 2.0)  # float not OK!
        self.assertRaises(TypeError, f.__pow__, g)    # another function?
No!
        self.assertRaises(ValueError, f.__pow__, -1)  # negative number? No!

    def test_powering(self):
        self.assertEqual((f*f)(10), 10000, "2nd power of 2nd power")
        self.assertEqual((f**3)(4), f(f(f(4))), "Powering broken")
        h = (f**3) * (g**2)
        self.assertEqual(h(-11), f(f(f(g(g(-11))))), "Powering broken")
        self.assertEqual((f**0)(100), 100, "Identity function")

if __name__ == "__main__":
    unittest.main()
_______________________________________________
Edu-sig mailing list
Edu-sig@python.org
https://mail.python.org/mailman/listinfo/edu-sig

Reply via email to