Overview

Python is not considered to be a functional language, however it does support the functional paradigm. The Python documentation provides a gentle introduction to functional programming with excellent narrative. As I continue to learn ML, I wanted to share a few interesting concepts around higher-order functions with a few examples written in Python.

What is a higher-order function?

In Python, functions are first-class which means that a developer can pass functions as arguments to other functions, a function can return a function, and a function can be assigned to a variable or stored in some data structure.

A higher-order function, in contrast, is a function that either (or both)

  • takes one or more functions as arguments
  • returns a function as its result

The map built-in function is an excellent example of a higher order function as you pass a function as an argument:

>>> list(map(lambda x: x**x, [1, 2, 3]))
[1, 4, 27]

A decorator is also a higher-order function because it takes a function as an argument and returns a function:

def twice(func):
    def caller(**args):
        func(**args)
        func(**args)
    return caller


@twice
def work():
    print("Doing work")

The work function will be executed twice because it has been decorated with the twice decorator:

>>> work()
Doing work
Doing work

Examples of higher-order functions

Call a function multiple times re-using the result

A function that given x, will call f(x) the n times. The result of each call will become input for the subsequent call. For instance, do_ntimes(lambda x: x+x, 3, 1) is 8 because 1 + 1 = 2 and now it’s 2 + 2 = 4 and then finally 4 + 4 = 8. This happens using a recursive call.

def do_ntimes(f, n, x):
    """Higher-order function that will do an operation f
    on x the n times.
    >>> do_ntimes(lambda x: x+x, 3, 1)
    8
    >>> do_ntimes(lambda x: x+x, 10, 1)
    1024
    """
    if n == 0:
        return x
    else:
        return f(do_ntimes(f, n - 1, x))

Function composition with two functions

Function composition is the process of combining two function calls into a single one.

def compose_two(f, g):
    """Function composition of two functions.
    >>> compose_two(f=lambda x: x + 10, g=lambda x: x - 5)(10)
    15
    >>> compose_two(f=lambda x: x - 25, g=lambda x: x * 10)(10)
    75
    """
    fg = lambda x: f(g(x))
    return fg

It is possible to pass the lambda functions directly or by assigning them to variables first:

>>> compose_two(f=lambda x: x + 10, g=lambda x: x - 5)(10)
15
>>> add10 = lambda x: x + 10
>>> minus5 = lambda x: x - 5
>>> compose_two(f=add10, g=minus5)(10)
15

Reducing to calculate the factorial

In addition to a recursion based solution (or a plain loop), it’s also possible to calculate factorial of a number using the functools.reduce:

from functools import reduce
from operator import mul


def factorial_reduce(n):
    """Calculate factorial.
    >>> factorial_reduce(5)
    120
    """
    return reduce(mul, range(1, n + 1), 1)

Reducing a chain of function calls

The function composition example can be rewritten in a standalone call. This is done using the built-in functools.reduce() function call. The result of the first function execution becomes the input argument for the next function. In the example below, the operation is ( ( (0 + 10) * 3 ) / 2 ).

>>> reduce(lambda res, f: f(res), 
[lambda n: n + 10, lambda n: n * 3, lambda n: n / 2], 0)
15.0

Function composition with arbitrary number of functions

Building up on the example above, the compose function can take arbitrary arguments wrapped up in a tuple.

def compose(*functions):
    """Function composition of arbitrary number of functions.
    >>> compose(*(lambda x: x + 2, lambda x: x + 5, lambda x: x - 6))(10)
    11
    >>> compose(*(lambda x: x + 2,))(10)
    12
    """
    fg = lambda value: reduce(lambda res, f: f(res), functions, value)
    return fg

Currying to check if elements are sorted

When one is currying a function, one is converting a function that takes multiple arguments into a sequence of functions that each take a single argument.

def are_3elems_sorted():
    """Use currying to check if three elements are sorted.
    >>> ((are_3elems_sorted()(1))(2))(3)
    True
    >>> ((are_3elems_sorted()(1))(4))(3)
    False
    """
    return lambda x: lambda y: lambda z: z > y > x

Happy functioning!


Published

Category

python

Tags