Blog

Akava is a technology transformation consultancy delivering

delightful digital native, cloud, devops, web and mobile products that massively scale.

We write about
Current & Emergent Trends,
Tools, Frameworks
and Best Practices for
technology enthusiasts!

Functional Programming using Python

Functional Programming using Python

Onel Harrison Onel Harrison
10 minute read

Listen to article
Audio generated by DropInBlog's Blog Voice AI™ may have slight pronunciation nuances. Learn more

Contents

  1. What is functional programming?
  2. Functions, functions, functions
  3. Immutable data types
  4. Useful modules
  5. Conclusion
  6. References

Python is a general purpose, multi-paradigm programming language. That means a programmer using Python can write just about any type of program using one or more programming paradigms.

Programming paradigms are a manner of programming that emphasize or constrain particular programming practices, patterns, or techniques. In other words, programming paradigms represent an opinionated view of how to write programs.

Among the paradigms afforded by Python are the imperative, object-oriented, and functional programming paradigms. This article focuses on the last of these, which in recent years has become more popular in industry settings.

What is functional programming?

Functional programming is a style of programming that centers the use of functions and immutable data types. These functions perform operations on data available to them as bound variables (also known as parameters), free variables (that exist outside the functions’ scope), and local variables (defined inside the function).

Also, these functions are ideally stateless, meaning they do not maintain any internal state that could change the functions’ output, regardless of how often the functions are called. All that to say, the outputs of these functions depend solely on their inputs.

The emphasis on immutable data types also contributes to this discipline as it prevents problems that stem from functions sharing mutable state such as race conditions.

Additionally, side-effects (which are effects caused by a running program that are external to the program) are treated with much intention. Side-effects are necessary in most useful programs, but they can also present several challenges such as carefully managing effect sequencing. Functions that do not have any side-effects are called pure functions while those that produce side-effects are called impure functions.

To solve problems using functional programming is to decomposed them into a set of these ideally stateless, pure (and minimally impure) functions that are then composed into a final solution (Kuchling, n.d). The result of programming in the functional style is often highly composable and modular programs that are easy to debug and test (Kuchling, n.d.).

Functions, functions, functions

Image credit: Author

First-class functions

In some languages, functions are merely defined or called. However, languages that enable functional programming support functions as first-class citizens. In such languages, functions are ordinary values, which means functions can be stored in variables and passed to or returned from other functions, just as other values like integers and strings can be. The code snippet below demonstrates these very things.

from typing import Callable

def double(n: int) -> int:
    return 2 * n

def is_even(n: int) -> bool:
    return n % 2 == 0

def make_adder(n: int) -> Callable[int, int]:

    def adder(m: int) -> int:
        return n + m

    return adder

if __name__ == "__main__":
    # 1. Functions can be assigned to variables
    times2 = double
    print(times2(21)) # 42

    # 2. Functions can be passed to other functions arguments
    evens_under10 = list(filter(is_even, range(10)))
    print(evens_under10) # [0, 2, 4, 6, 8]

    # 3. Functions can be returned from functions
    add10 = make_adder(10)
    print(add10(5)) # 15
Code snippet showing examples of what’s possible in a language with first-class functions

The presence of first-class functions in a language is foundational to its ability to enable functional programming. Keep this in mind while reading the rest of this post as you will notice that many of the language features discussed herein would not be possible without first-class functions.

Higher-order functions

higher-order function is a function that takes at least one function as an argument, returns a function as its output, or does both. In the code snippet above, the filter and make_adder functions are examples of higher-order functions. In addition to filter, Python has other frequently-used built-in higher-order functions such as map and sorted.

Python decorators are also syntax sugar over higher-order functions as shown in the code snippet below.

def log_before(f):
    def _wrapper(*args, **kwargs):
        print("logging before...")
        f(*args, **kwargs)

    return _wrapper


def log_after(f):
    def _wrapper(*args, **kwargs):
        f(*args, **kwargs)
        print("logging after...")

    return _wrapper


@log_before
@log_after
def greet(name: str):
    print(f"Hello {name}")


# with the log_before and log_after decorators
# the following is semantically equivalent to
# `log_before(log_after(greet))("John")`
greet("John")
Code snippet showing Python decorators as higher-order functions

Anonymous functions

An anonymous function is a function without a name. In Python, they are created using the lambda keyword. Python only allows anonymous function to include a single expression, which befits another name for an anonymous function — function expression.

In Python, anonymous functions are often used in conjunction with high-order . The code snippet below shows anonymous functions being used with common built-in higher-order functions in Python.

from collections import namedtuple

Person = namedtuple("Person", ["name", "age"])

if __name__ == "__main__":
    people = [
        Person("Marie Curie", 66),
        Person("Katherine Johnson", 101),
        Person("Ada Lovelace", 36),
    ]

    first_names = list(map(lambda p: p.name.split(" ")[0], people))
    print(first_names)  # ["Marie", "Katherine", "Ada"]

    # Filter the list to only people with name starting with 'A' and print it
    people_with_names_starting_with_a = list(filter(lambda p: p.name.startswith("A"), people))
    print(people_with_names_starting_with_a)

    # Sort the list by name and print it
    print(sorted(people, key=lambda p: p.name))

    # Sort the list by age and print it
    print(sorted(people, key=lambda p: p.age))
Code snippet showing the use of anonymous functions with higher-order built-in functions

The output of the above snippet is shown below.

['Marie', 'Katherine', 'Ada']
[Person(name='Ada Lovelace', age=36)]
[Person(name='Ada Lovelace', age=36), Person(name='Katherine Johnson', age=101), Person(name='Marie Curie', age=66)]
[Person(name='Ada Lovelace', age=36), Person(name='Marie Curie', age=66), Person(name='Katherine Johnson', age=101)]

Pure vs impure functions

As previously mentioned, functions that do not have any side-effects (e.g. I/O operations and global state modification) are called pure functions while those that produce side-effects are called impure functions.

def strip_pure(sentence: str) -> str:
    return sentence.strip()


strip_impure_call_count = 0


def strip_impure(sentence: str) -> str:
    global strip_impure_call_count

    stripped_sentence = sentence.strip()

    # Side effect 1: modify global state
    strip_impure_call_count += 1
    # Side effect 2: output to stdout
    print(f"Called strip_impure {strip_call_count} times")

    return stripped_sentence
Code snippet showing pure vs impure functions in Python

Predicate functions

Predicate functions map their arguments to a true or false value. They have been use multiple times in this post so far. The named is_even function and the lambda p: p.name.startswith("A") anonymous function shown in the Higher-order functions and Anonymous functions sections, respectively, are examples of predicate functions.

Immutable data types

Python has many built-in immutable data types. Most of these are primitive-like data types such as strings, ints, floats, and bool. But there are also immutable collection, container, and sequence data types such as tuples and ranges.

⚠️Warning! Immutable collection/container objects such as tuples may contain mutable objects such as lists, dictionaries, and custom collection/container-like objects. For instance, while a tuple itself may be immutable, the objects it contains may be mutable and can be modified.

Because mutation is minimized in the functional paradigm, it is easy to view programs as performing a series of reproducible data transformations. In the functional style, data-transforming functions and operations that seem to perform mutations typically result in the creation of new objects. For these reason, functional programming languages have historically been criticized for being less performant than their imperative counterparts.

However, many advancements in optimizing compilers and data structures (such as immutable persistent data structures) have quelled inefficiency concerns for the common programming scenarios, which allows programmers to get all the benefits of using the functional style without fear of significant performance loss.

Useful modules

operator

The operator module exports a set of efficient functions corresponding to the intrinsic operators of Python… The functions fall into categories that perform object comparisons, logical operations, mathematical operations and sequence operations.
operator

The operator module provides the standard Python operators such as addition and multiplication as functions. For example, there are the operator.add and operator.mul functions for the addition and multiplication operators, respectively.

The provided operators-as-functions allow us to use Python’s operators more flexibly and in ways that fit the functional style more closely.

functools

The functools module is for higher-order functions: functions that act on or return other functions. In general, any callable object can be treated as a function for the purposes of this module.
functools

As its description highlights, the functools module has some useful functions for working with higher-order functions. The ones I tend to reach for most often include cachereducepartial, and wraps.

Among these, perhaps the most important one to highlight is partial as it enables us to easily perform partial application, a critical technique to grasp in functional programming.

Partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity [i.e., number of arguments].
Wikipedia (Partial application)

The arguments we fix when partially applying a function are those whose values we don’t anticipate will change across several invocations of the function. Consequently, partial application leads to terser code because each subsequent invocation of the partially applied function usually takes up less screen space.

Additionally, we may want to partially apply a function to give it an arity that would allow us to compose it with other functions.

itertools

This module implements a number of iterator building blocks inspired by constructs from APL, Haskell, and SML… The [itertools] module standardizes a core set of fast, memory efficient tools that are useful by themselves or in combination… These tools and their built-in counterparts also work well with the high-speed functions in the operator module.

The itertools module has a lot of functions that allow Python programmers to conveniently and efficiently work with collection and sequence data types.

Conclusion

Python is a general purpose, multi-paradigm programming language. Among the many paradigms it supports is the functional programming paradigm, a style of programming that centers the use of functions and immutable data types. This post highlights essential functional programming concepts, patterns, and tools to begin writing more functionally-styled Python.

Essential concepts and patterns highlighted in this post include higher-order functions, first-class functions, pure and impure functions, predicate functions, and partial application. Built-in modules such as operator , functools , and itertools that fit well with the functional style are also brought to the fore. Third-party libraries such as toolz may also be of interest.

Finally, if your curiosity is still raging, other topics to investigate include closurecurryinglazy and strict evaluation, and immutable persistent data structures.

References

  1. Kuchling, A. M. (n.d.). Functional Programming HOWTO (v. 0.32). Python 3 Documentation. Retrieved from https://docs.python.org/3/howto/functional.html
  2. Python 3 operator module
  3. Python 3 functools module
  4. Python 3 itertools module
  5. Python decorators


Akava would love to help your organization adapt, evolve and innovate your modernization initiatives. If you’re looking to discuss, strategize or implement any of these processes, reach out to [email protected] and reference this post.

« Back to Blog