Functional Programming in Python

Essential concepts, patterns, and modules

Functional Programming in Python

Essential concepts, patterns, and modules


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.

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

A 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.

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.

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.

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 cache, reduce, partial, 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 closure, currying, lazy 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