{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Python for Everyone!
[Oregon Curriculum Network](http://4dsolutions.net/ocn/)\n", "\n", "
\n", "

Composition of Functions

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The function type (types.FunctionType) is not one to multiply by others of that type. If f and g are both functions, \"h = f \\* g\" does not initially make any sense.\n", "\n", "However it does make sense to multiply two functions, giving a function result, when \"to multiply\" means \"to compose\" such that (f * g)(x) is equivalent to f(g(x)). \n", "\n", "The Composable class below eats a function to create an instance of itself that both multiplies (composes with other functions) and powers (composes with itself). \n", "\n", "pow(Composable(f), 5) is equivalent to lambda x: f(f(f(f(f(x))))). \n", "\n", "Calling the instance with an argument returns the same result the original function would have: f(x) == Composable(f)(x)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(f * g)(7): 81\n", "(g * g)(7): 51\n" ] } ], "source": [ "import types # <-- to get FunctionType\n", "\n", "class Composable:\n", " \"\"\"\n", " Composable swallows a function, which may still be called, by\n", " calling the instance instead. Used as a decorator, the Composable\n", " class enables composition of functions by means of multiplying\n", " and powering their corresponding Composable instances.\n", " \"\"\"\n", " \n", " def __init__(self, func):\n", " self.func = func # eat a callable\n", " \n", " def __call__(self, x):\n", " return self.func(x) # still a callable\n", " \n", " def __mul__(self, other):\n", " \"\"\"\n", " multiply two Composables i.e. (f * g)(x) == f(g(x))\n", " g might might a function. OK if f is Composable.\n", " \"\"\"\n", " if isinstance(other, types.FunctionType): # OK if target is a function\n", " other = Composable(other)\n", " if not isinstance(other, Composable): # by this point, other must be one\n", " raise TypeError\n", " \n", " return Composable(lambda x: self.func(other.func(x))) # compose 'em\n", " \n", " def __rmul__(self, other): # in case other is on the left\n", " \"\"\"\n", " multiply two Composers i.e. (f * g)(x) == f(g(x))\n", " f might might a function. OK if g is Composer.\n", " \"\"\"\n", " if isinstance(other, types.FunctionType): # OK if target is a function\n", " other = Composable(other)\n", " if not isinstance(other, Composable): # by this point, other must be a Composer\n", " raise TypeError\n", " return Composable(lambda x: other.func(self.func(x))) # compose 'em\n", " \n", " def __pow__(self, exp):\n", " \"\"\"\n", " A function may compose with itself why not?\n", " \"\"\"\n", " # type checking: we want a non-negative integer\n", " if not isinstance(exp, int):\n", " raise TypeError\n", " if not exp > -1:\n", " raise ValueError\n", " me = self\n", " if exp == 0: # corner case\n", " return Composable(lambda x: x) # identify function\n", " elif exp == 1:\n", " return me # (f**1) == f\n", " for _ in range(exp-1): # e.g. once around loop if exp==2\n", " me = me * self\n", " return me\n", " \n", " def __repr__(self):\n", " return \"Composable({})\".format(self.func.__name__)\n", "\n", "@Composable \n", "def f(x):\n", " \"second powering\"\n", " return x ** 2\n", "\n", "@Composable\n", "def g(x):\n", " \"adding 2\"\n", " return x + 2\n", "\n", "print(\"(f * g)(7):\", (f * g)(7)) # add 2 then 2nd power\n", "print(\"(g * g)(7):\", (g * f)(7)) # 2nd power then add 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Suppose we wish to develop unit tests for such as the above, to show ourselves the intended functionality. Here's an example test suite:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "....\n", "----------------------------------------------------------------------\n", "Ran 4 tests in 0.006s\n", "\n", "OK\n", "All tests passed!\n" ] } ], "source": [ "import unittest\n", "import sys\n", "\n", "class TestComposer(unittest.TestCase):\n", "\n", " def test_simple(self):\n", " x = 5\n", " self.assertEqual((f*g*g*f*g*f)(x), f(g(g(f(g(f(x)))))), \"Not same!\")\n", " \n", " def test_function(self):\n", " def addA(s): # not decorated\n", " return s + \"A\"\n", " @Composable\n", " def addM(s): \n", " return s + \"M\" \n", " \n", " addAM = addM * addA # Composable times regular function, OK?\n", " self.assertEqual(addAM(\"I \"), \"I AM\", \"appends A then M\")\n", " addMA = addA * addM # regular function, times Composable OK?\n", " self.assertEqual(addMA(\"HI \"), \"HI MA\", \"appends M then A\")\n", " \n", " def test_inputs(self):\n", " @Composable \n", " def f(x):\n", " \"second powering\"\n", " return x ** 2\n", " \n", " self.assertRaises(TypeError, f.__pow__, 2.0) # float not OK!\n", " self.assertRaises(TypeError, f.__pow__, g) # another function? No!\n", " self.assertRaises(ValueError, f.__pow__, -1) # negative number? No!\n", " \n", " def test_powering(self):\n", " @Composable \n", " def f(x):\n", " \"second powering\"\n", " return x ** 2\n", " @Composable\n", " def g(x):\n", " \"adding 2\"\n", " return x + 2\n", " \n", " self.assertEqual((f*f)(10), 10000, \"2nd power of 2nd power\")\n", " self.assertEqual(pow(f, 3)(4), f(f(f(4))), \"Powering broken\") \n", " h = (f**3) * (g**2)\n", " self.assertEqual(h(-11), f(f(f(g(g(-11))))), \"Powering broken\")\n", " self.assertEqual((f**0)(100), 100, \"Identity function\")\n", " \n", "the_tests = TestComposer() \n", "suite = unittest.TestLoader().loadTestsFromModule(the_tests)\n", "output = unittest.TextTestRunner(stream=sys.stdout).run(suite)\n", "if output.wasSuccessful():\n", " print(\"All tests passed!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We're good!\n", "\n", "Algebra City series. By Kirby Urner (copyleft) MIT License, 2016" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.1" }, "widgets": { "state": {}, "version": "1.1.2" } }, "nbformat": 4, "nbformat_minor": 0 }