[ Jocelyn Ireson-Paine's Home Page ]

Python Functions as First-Class Values

This is part of a tutorial I gave at The Oxford Institute, to demonstrate the idea of functions as first-class values.

Python has various kinds of data. For example integers, strings, Booleans, and lists. They can all be assigned to variables:

>>> v = 42
>>> v
42
>>> v = "A string"
>>> v
'A string'
>>> v = True
>>> v
True
>>> v = [1,2,3,4]
>>> v
[1, 2, 3, 4]
>>>

They can also be assigned to parts of other data objects:

>>> l = [42]
>>> l[0]
42
>>> l = ["A string"]
>>> l[0]
'A string'
>>> l = [True]
>>> l[0]
True
>>>

They can be passed as arguments to functions:

>>> def printme(x):
...   print(x)
...   return
...
>>> printme(42)
42
>>> printme("A string")
A string
>>> printme(True)
True
>>>

And they can be returned from functions:

>>> def returnme(x):
...   return x
...
>>> returnme(42)
42
>>> returnme('A string')
'A string'
>>> returnme(True)
True
>>>

We expect programming languages to be able to do such things with numbers. It would be hard to program numerical maths otherwise. Nowadays, we expect it of strings, Booleans and other kinds of data too. But this wasn't the case in early languages: for example, Algol 60. The language had strings, but you couldn't assign them to variables, as Boyko Bantchev explains in his notes on Algol 60:

Particularly notable is the very limited presence of strings. Indeed, the language defines the notion of strings but leaves them almost unusable in practice. Here is why.

There are string constants but not string variables other than formal parameters within procedures. Actually, strings are not even values, since it is impossible to create new strings in the program. Besides, Algol's strings are monolithic: one cannot specify individual items within them (there is no character datatype anyway) or extract substrings. In fact, the only things one can do with strings are printing them, passing them as arguments to procedures, finding the length of a parameter string, and using them as reference tables in single-character input or output (rather than inputting a character per se, one obtains its index within the reference table; reversely for output). Even computing the length of the string in the presence of inner strings is implementation-dependent.

Similar to strings, other objects present in the language are 'half-data'. Arrays, labels, switches, and procedures can be passed as arguments but are not assignable values, do not constitute datatypes, and cannot be the result of a procedure. The only true kinds of values, and the only datatypes in Algol 60 are integers, reals and booleans.

When a language lets you do all these things to a piece of data, computer scientists call it a first-class value. The phrase was coined by the famous computer scientist Christopher Strachey:

First and second class objects. In Algol, a real number may appear in an expression or be assigned to a variable, and either of them may appear as an actual parameter in a procedure call. A procedure, on the other hand, may only appear in another procedure call either as the operator (the most common case) or as one of the actual parameters. There are no other expressions involving procedures or whose results are procedures. Thus in a sense procedures in Algol are second class citizens—they always have to appear in person and can never be represented by a variable or expression (except in the case of a formal parameter)...

From Strachey's "Fundamental Concepts in Programming Languages" in Higher-Order and Symbolic Computation Volume 13 Issue 11 (2000); though published in 2000, these are notes from lectures Strachey delivered in August, 1967.

Nowadays, most languages do treat strings as first-class values. But what about functions? Some languages still don't let you use functions as data. For example, Java:

But in Python, you can:

>>> f = lambda x: x+2
>>> f
<function <lambda> at 0x7fd820430668>
>>> f(1)
3
>>> f(3)
5
Here, the variable f holds a function. The "lambda(x): x+2" is its value, just as in "v = 42", 42 becomes v's value.

You can put the function in a list:

>>> l = [f]
>>> l[0]
<function <lambda> at 0x7fd820430668>
>>>

And you can pass it to another function:

>>> printme(f)
<function <lambda> at 0x7fd820430668>
>>>

And as shown above, you can call it. Wherever it is:

>>> f(1)
3
>>> l = [f]
>>> l[0](1)
3
>>>

You don't even have to put it in a variable:

>>> (lambda x: x+2)(1)
3
>>>

Python actually has built-in functions that take functions as arguments. One is 'map':

>>> map( f, [1,2,3,4] )
[3, 4, 5, 6]
>>> map( lambda x:x+2, [1,2,3,4] )
[3, 4, 5, 6]
>>>

For my finale, here's a function that makes a new function from functions passed as its arguments:

>>> double = lambda x: x*2
>>> add_10 = lambda x: x+10
>>> compose = lambda f, g: (lambda x: g(f(x)))
>>> c = compose( double, add_10 )
>>> d = compose( add_10, double )
>>> c(3)
16
>>> d(3)
26
>>>