Last week we introduced higher-order functions. Particularly, how to deal with functions as parameters. On this post, we will see how to return a function from a function as well as an example.
Higher-order functions: motivation
As a reminder, a higher-order function is a function that either takes another function as a parameter or that returns another function as its result. To understand its usefulness, imagine it wasn’t possible to create higher-order functions. That would impose a limitation on the expressive power of the programming language.
On the other hand, being able to define higher-order functions allows us to create abstractions that otherwise wouldn’t be possible. We’ll see a simple example below. For now, we can just keep in mind that higher-order functions allow for greater expressive power.
Its importance is such that, in the past, programming languages that allowed to define higher-order functions were especially distinguished. On these programming languages, it was recognized that functions were treated as first-class citizens. Which meant that they had the following “privileges”:
1. They may be named by variables.
SICP
2. They may be passed as arguments to procedures.
3. They may be returned as the results of procedures.
4. They may be included in data structures.
See that we defined higher-order functions precisely as satisfying points 2. and 3. (understand “procedure” as “function” on the quotation above).
Defining higher-order functions
In this section, we’ll see how to define a function that returns another function in Python. We’ll see two variations.
Method 1: Helper function
The first way to do it will be by including a function definition inside the body of the main function. Then we return this function.
def main_function(...): # Main function body def helper_function(...): # helper function code # Main function code return helper_function
See that helper_function
is defined inside the body of main_function
. Additionally,main_function
returns helper_function
. Syntactically, there isn’t anything very special when defining a higher-order function. We return a function as we would do for any regular variable.
Let’s see an example.
Imagine that we have a simple function that returns the sum of two values a + b
. We could create a regular function that accepts arguments a and b and returns their sum. Instead, we’ll create a function that, given a
, will create a function that will accept one parameter and add a
directly.
def create_adder(a): def add(b): return a + b return add
create_adder
returns the function add
which accepts one argument (b
). Function add
will make use of the argument a
passed initially to create_adder
.
>>> add1 = create_adder(1) >>> add1(4) 5
See that in order to use the function returned by create_adder
, we assign it to add1
. Then we can call add1
one numerical argument.
Alternatively, we could immediately call the function returned by create_adder
:
>>> create_adder(1)(4) 5
The first argument, 1
, is passed to create_adder
. The second argument, 4
, is passed to the function returned by create_adder
.
Method 2: Anonymous functions (lambda)
We can see lambda expressions in Python as a syntactic sugar to define functions. The name “lambda” is derived from lambda calculus, which gave a mathematical basis to computational models and influenced several programming languages.
The basic syntax is:
lambda a1, a2, ..., an: <expression>
lambda
is a special keyword, just as def
is. Both are used to define functions. a1, a2
, etc are the function parameters. Each parameter is separated by commas. The “body” of the lambda function comes after the colons :
. In practice, <expression>
corresponds to the return
statement of a “normal” function definition. This imposes a limitation on lambda functions to a single line.
Let’s see a “normal” function definition and its equivalent using lambda functions.
def normal_function(x): return x + 1 lambda_function = lambda x: x + 1
Both normal_function
and lambda_function
are equivalent.
>>> normal_function(1) 2 >>> lambda_function(1) 2
Lambda expressions are mostly used in functional programming languages. You can see an example of recursive lambda functions in Scheme here.
In Python, its use is acceptable for the definition of very simple functions (as syntactic sugar). For more complex functions, lambda expressions are disfavored and the use of “normal” function definitions are encouraged. While lambda definitions offer more flexibility, using them excessively can make the code harder to understand.
Coming back to the example we used above, the definition of an adder function, we can change its definition using lambda expressions as follows:
def lambda_adder(a): return lambda b: a + b
We can compare this definition with the example above. This is when the advantages of lambda expressions become apparent. We have reduced the definition of our higher-order procedure to a single line!
Its utilization is similar to the one we had above:
>>> add1 = lambda_adder(1) >>> add1(3) 4 >>> add1(-1) 0
With this, we have finished our introduction to higher-order procedures. On future posts, we’ll see its application on more elaborate examples.