A good set of tools
We call this "using together" composition
# We reuse the following binary operators from last time
def add(x, y):
return x + y
def mul(x, y):
return x * y
def lesser(x, y):
if x < y:
return x
else:
return y
def greater(x, y):
if x > y:
return x
else:
return y
# Also consider the following higher order functions
map
reduce
from toolz import accumulate
In this section we will compose binary operators with higher order functions.
First we need to introduce accumulate
, reduce's brother, and the multiargument behavior of map
. We'll start with accumulate
.
# When showing off numpy we used the cumulative sum method
import numpy as np
X = np.arange(20)
X[X>=10].cumsum()
array([ 10, 21, 33, 46, 60, 75, 91, 108, 126, 145])
# Accumulate is a general version of this
data = range(10, 20)
list(accumulate(add, data))
[10, 21, 33, 46, 60, 75, 91, 108, 126, 145]
Ideally these tools together cover most relevant tasks
X[X>=10].cumprod()
array([ 10, 110, 1320, 17160, 240240, 3603600, 57657600, 980179200, 17643225600, 335221286400])
list(accumulate(mul, data))
[10, 110, 1320, 17160, 240240, 3603600, 57657600, 980179200, 17643225600, 335221286400]
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"
Combined with one of several bits however an auto-torquer takes on a variety of roles.
Furthermore these same bits can be used in other torquers like a socket-wrench or ratcheting screwdriver.
Composition of tools enables a wide variet of solutions from a relatively compact set of toolz.
We'll also need to show how map
can operate on binary operators.
We've seen three similar functions like the power-drill, map
, reduce
, and accumulate
. Each of them can work with binary operators.
data1 = [1, 2, 3, 4, 5]
data2 = [10, 20, 30, 40, 50]
map(add, data1, data2)
[11, 22, 33, 44, 55]
reduce(lesser, [5, 3, 2, 7, 3], 999999999)
2
list(accumulate(lesser, [5, 3, 2, 7, 3]))
[5, 3, 2, 2, 2]
map(lesser, [5, 3, 2, 7, 3], [1, 9, 2, 6, 6]) # Pairwise lesser across the two lists
[1, 3, 2, 6, 3]
So now we have four binary operators
add, mul, lesser, greater
And three higher order functions that can leverage binary operators
map, reduce, accumulate
And we can meaningfully combine any pair of them
Create a new binary operator, longer
, which returns the longer of two elements
def longer(a, b):
...
assert longer('cat', 'mouse') == 'mouse' # because cat is of length 3 and mouse is of length 5
File "<ipython-input-11-663766df1d5d>", line 2 ... ^ SyntaxError: invalid syntax
Use this binary operator together with reduce
, accumulate
, and map
to compute the following
longest word, the longest word over time, and the longest between pairs
# Use reduce to find the longest word
animals = ['cat', 'mouse', 'lion', 'goose', 'giraffe', 'mule']
# Use accumulate to track the longest word over time.
# This should be a list where the first element is 'cat', the last is 'giraffe',
# and each intermediate element is the longest word up to that point
# Use map to create a list of the pairwise longest among the following two lists
animals = ['cat', 'mouse', 'lion', 'goose', 'giraffe', 'mule']
fruits = ['apple', 'orange', 'banana', 'date', 'grape', 'strawberry']
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.
This principle of composition extends beyond this particular design.