{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Composition\n", "\n", "A good set of tools \n", "\n", "* Contains items with simple purposes. For example a hammer or wrench\n", "* Can be used together for complex tasks. For example we can hit a wrench with the hammer to loosen a tight nut\n", "\n", "We call this \"using together\" composition" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# We reuse the following binary operators from last time\n", "def add(x, y):\n", " return x + y\n", "\n", "def mul(x, y):\n", " return x * y\n", "\n", "def lesser(x, y):\n", " if x < y:\n", " return x\n", " else:\n", " return y\n", "\n", "def greater(x, y):\n", " if x > y:\n", " return x\n", " else:\n", " return y" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "# Also consider the following higher order functions\n", "map\n", "reduce\n", "from toolz import accumulate" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this section we will compose binary operators with higher order functions.\n", "\n", "First we need to introduce `accumulate`, reduce's brother, and the multiargument behavior of `map`. We'll start with `accumulate`." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# When showing off numpy we used the cumulative sum method\n", "\n", "import numpy as np\n", "X = np.arange(20)\n", "\n", "X[X>=10].cumsum()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 3, "text": [ "array([ 10, 21, 33, 46, 60, 75, 91, 108, 126, 145])" ] } ], "prompt_number": 3 }, { "cell_type": "code", "collapsed": false, "input": [ "# Accumulate is a general version of this\n", "\n", "data = range(10, 20)\n", "\n", "list(accumulate(add, data))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "[10, 21, 33, 46, 60, 75, 91, 108, 126, 145]" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ideally these tools together cover most relevant tasks" ] }, { "cell_type": "code", "collapsed": false, "input": [ "X[X>=10].cumprod()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": [ "array([ 10, 110, 1320, 17160,\n", " 240240, 3603600, 57657600, 980179200,\n", " 17643225600, 335221286400])" ] } ], "prompt_number": 5 }, { "cell_type": "code", "collapsed": false, "input": [ "list(accumulate(mul, data))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "[10,\n", " 110,\n", " 1320,\n", " 17160,\n", " 240240,\n", " 3603600,\n", " 57657600,\n", " 980179200,\n", " 17643225600,\n", " 335221286400]" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Power tools and bits\n", "\n", "Consider the device commonly known as a power drill. On its own this device is useless, merely spinning an empty socket. It is more accurately called a \"power spinner\" or \"auto-torquer\"\n", "\n", "Combined with one of several bits however an auto-torquer takes on a variety of roles.\n", "\n", "Furthermore these same bits can be used in other torquers like a socket-wrench or ratcheting screwdriver.\n", "\n", "Composition of tools enables a wide variet of solutions from a relatively compact set of toolz. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll also need to show how `map` can operate on binary operators.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Power functions\n", "\n", "We've seen three similar functions like the power-drill, `map`, `reduce`, and `accumulate`. Each of them can work with binary operators." ] }, { "cell_type": "code", "collapsed": false, "input": [ "data1 = [1, 2, 3, 4, 5]\n", "data2 = [10, 20, 30, 40, 50]\n", "map(add, data1, data2)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "[11, 22, 33, 44, 55]" ] } ], "prompt_number": 7 }, { "cell_type": "code", "collapsed": false, "input": [ "reduce(lesser, [5, 3, 2, 7, 3], 999999999)" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": [ "2" ] } ], "prompt_number": 8 }, { "cell_type": "code", "collapsed": false, "input": [ "list(accumulate(lesser, [5, 3, 2, 7, 3]))" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 9, "text": [ "[5, 3, 2, 2, 2]" ] } ], "prompt_number": 9 }, { "cell_type": "code", "collapsed": false, "input": [ "map(lesser, [5, 3, 2, 7, 3], [1, 9, 2, 6, 6]) # Pairwise lesser across the two lists" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 10, "text": [ "[1, 3, 2, 6, 3]" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So now we have four binary operators\n", "\n", " add, mul, lesser, greater\n", " \n", "And three higher order functions that can leverage binary operators\n", "\n", " map, reduce, accumulate\n", " \n", "And we can meaningfully combine any pair of them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise\n", "\n", "Create a new binary operator, `longer`, which returns the longer of two elements" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def longer(a, b):\n", " ...\n", " \n", "assert longer('cat', 'mouse') == 'mouse' # because cat is of length 3 and mouse is of length 5" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "SyntaxError", "evalue": "invalid syntax (, line 2)", "output_type": "pyerr", "traceback": [ "\u001b[1;36m File \u001b[1;32m\"\"\u001b[1;36m, line \u001b[1;32m2\u001b[0m\n\u001b[1;33m ...\u001b[0m\n\u001b[1;37m ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m invalid syntax\n" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use this binary operator together with `reduce`, `accumulate`, and `map` to compute the following\n", "\n", "longest word, the longest word over time, and the longest between pairs" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Use reduce to find the longest word\n", "\n", "animals = ['cat', 'mouse', 'lion', 'goose', 'giraffe', 'mule']" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 12 }, { "cell_type": "code", "collapsed": false, "input": [ "# Use accumulate to track the longest word over time.\n", "# This should be a list where the first element is 'cat', the last is 'giraffe', \n", "# and each intermediate element is the longest word up to that point\n" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 13 }, { "cell_type": "code", "collapsed": false, "input": [ "# Use map to create a list of the pairwise longest among the following two lists\n", "animals = ['cat', 'mouse', 'lion', 'goose', 'giraffe', 'mule']\n", "fruits = ['apple', 'orange', 'banana', 'date', 'grape', 'strawberry']\n", "\n" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recap\n", "\n", "The functional standard library covers a wide variety of applications with a small set of functions. It acheives this by a carefully designed higher order functions. By design these functions are highly *composable*, that is, they can be *used together* with both with each other and with unanticipated user code.\n", "\n", "This principle of composition extends beyond this particular design." ] } ], "metadata": {} } ] }