"
]
},
{
"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
}