[Tutor] Higher-Order Function Examples

Steven D'Aprano steve at pearwood.info
Mon Feb 14 12:30:46 CET 2011


Carla Jenkins wrote:

> Hello everyone: I am new to Python and am looking for higher-order
> function programming examples.  The programming codes do not feature
> the corresponding equation so I am lost. Please help me.

The first thing you need to understand is the difference between *code* 
and *data*.

Code, or programs, or functions, is the thing that does stuff. That's a 
bit vague, but you should get the idea: you can have a function to print 
a list of telephone addresses, or control a microwave oven, or delete 
unneeded records in a database. You know,... stuff.

Data is the things that functions work on: numbers, strings, lists of 
data, addresses, and things like that. Data doesn't do anything, it just 
sits there in a file, or in a variable, waiting for a function to do 
stuff with it.

The idea for "higher-order functions" came from one simple, but radical, 
concept: the idea that functions themselves can ALSO be data. The idea 
is simple: it's like the idea of building a tool to build the tool you 
actually need. But this was quite a radical idea, because many computer 
languages enforced a strict separation of functions and data.

Fortunately Python is not one of those languages. In Python, we say that 
"functions are first-class objects" -- you can treat functions like 
numbers, or strings, or lists, or any other data. The difference is, you 
just refer to the function by name without following it with brackets:

len  # The built-in function itself.
len(x)  # Calling the function len on argument x.


So you can write one function that takes another function as argument, 
and does something useful with that function:

def draw_graph(function, x_min, x_max):
     # Draw a graph of function between x_min and x_max.
     ...

You won't believe how hard that is in languages like Pascal that don't 
have first class functions!

Here's a working example. Suppose you are doing a lot of string 
processing, using string.upper(), string.lower() etc. And you want to 
see what they do, when they get called. So you might write a function 
like this:

def pr_upper(s):
     print(s)
     return str.upper(s)  # you may be more familiar with the syntax
                          # s.upper() instead of str.upper(s)


and then instead of using str.upper, you use pr_upper instead. Now you 
want to do the same for str.lower, str.title, str.capitalize, and any 
other string functions you might have. This gets tedious and boring, 
writing all these little functions by hand. So, make a tool to make the 
tools you want! We write a *factory function* that creates new functions 
for us:

def make_pr(function):
     # Return a new function that prints the argument first.
     def inner(s):
         print(s)
         return function(s)
     return inner  # Return the inner function we just built.

pr_upper = make_pr(str.upper)
pr_lower = make_pr(str.lower)
pr_split = make_pr(str.split)

Of course, there's no reason why we're limited to built-in functions. It 
also works on our own functions:

def spammer(food):
     return "spam and %s" % food

spammer("eggs")  # returns "spam and eggs"

pr_spammer = make_pr(spammer)


make_pr is a higher-order function -- it's a function that treats other 
functions as data. Python come with at least four higher-order functions 
built in, plus some more in various libraries. I'm not going to talk 
about them all, but I'll mention three of them: map, filter and reduce.

(The fourth, apply, is obsolete and has been removed completely from 
Python 3. Don't worry about it, but if you care, you can look it up in 
the manual.)


(1) map takes a function and a sequence of values, and it applies the 
function to each value in the sequence. For example:

# convert items to strings
map(str, [1, 2, 3])  # Python 2
=> ['1', '2', '3']

In Python 3, map has been changed to be lazy. It no longer returns the 
new list all at once, but returns a special *iterator* object that gives 
one value at a time, on request. This is much more memory efficient, but 
you can get the old behaviour back by using the function list():

list(map(str, [1, 2, 3]))  # Python 3
=> ['1', '2', '3']



(2) filter takes a function and a sequence of values, and returns those 
values where function returns true. For example:

# filter only uppercase strings
filter(str.isupper, ['a', 'B', 'c', 'D'])  # Python 2
=> ['B', 'D']

Again, in Python 3, filter has become lazy.



(3) reduce is perhaps the trickiest of the built-in high-order 
functions. Again, it takes a function and a sequence of values. It grabs 
the first two values, and feeds them to the function to get a result. 
Then it takes that result, and the next value, and feeds them into the 
function to get the next result. And repeats, until there's no more values.

An example might help. Back in the old days, before Python had a sum() 
function, the easiest way to add up a list of numbers was to use reduce 
and a small function to add two numbers:

def add(x, y):
     return x+y

total = filter(add, [2, 4, 5, 9, 1])

Step by step, filter would do this:

Take the first two values, 2 and 4, and feed them to add:
add(2, 4) -> 6
Take it and the next value, 5, and feed them to add:
add(6, 5) -> 11
Take it and the next value, 9, and feed them to add:
add(11, 9) -> 20
Take it and the next value, 1, and feed them to add:
add(20, 1) -> 21
So the final result is 21.


A lot of people find reduce() too hard to understand, and so sadly in 
Python 3 it has been banished from the built-in functions into the 
functools library.



Hope this helps,




-- 
Steven



More information about the Tutor mailing list