The general computing paradigm has several states: the task to be performed; a strategy for how the task will be performed; transformation of the strategy into a detailed strategy that corresponds to computations that a computer can perform; implementation of the strategy as a set of instructions that can be executed by a computer; and, lastly, validation that the results of the execution perform the task in a satisfactory manner. In software engineering, these states are called requirements analysis, architecture and design, program code, and validation. This generic model has been refined into programming paradigms to support the transition from design to programming.
This unit introduces several programming paradigms and explains main concepts of the Object-Oriented programming paradigm. Each paradigm has several, in some cases, many, programming languages that support it. This page illustrates functional programming via Python. Note that it mentions some key features, including state, immutability, first class data type, recursion, anonymous functions, and lazy-evaluation.
Python supports multiple programming paradigms, but its not best known for its Functional Programming style.
As its own creator has mentioned before, Python hasnt been heavily influenced by other functional languages, but it does show some functional features. This is a very gentle introduction to Functional Programming in Python.
Functional Programming is a programming paradigm based on the evaluation of expression, which avoids changing-state and mutable data. Popular functional languages include Lisp (and family), ML (and family), Haskell, Erlang and Clojure.
Functional Programming can be seen as a subset of Declarative Programming (as opposed to Imperative Programming).
Some of the characteristics of Functional Programming are:
One of the key motivations beyond Functional Programming, and beyond some of its aspects like no changing-state and no mutable data, is the need to eliminate side effects, i.e. changes in the state that dont really depend on the function input, making the program more difficult to understand and predict.
In functional programming, functions transform input into output, without an intermediate representation of the current state.
Consider the following code:
def square(x): return x*x input = [1, 2, 3, 4] output =  for v in input: output.append(square(v))
The code takes a list as input and iterates through its individual values applying the function square() to them; the final outcome is stored in the variable output which is initially an empty list, and is updated during the iteration.
Now, let's consider the following functional style:
def square(x): return x*x input = [1, 2, 3, 4] output = map(square, input)
The logic is very similar: apply the function square() to all the element of the list given as input, but there is no internal state representation. The output is also a little bit different, as the map() function will return an iterator rather than a list.
Related to the point above, the concept of immutable data can be summarised as: functions will return new data as the outcome of a computation, leaving the original input intact.
As mentioned earlier, Python is not a purely functional language, so there are both mutable and immutable data structures, e.g. lists are mutable arrays, while tuples are immutable:
>>> mutable = [1, 2, 3, 4]
>>> mutable = 100
[100, 2, 3, 4]
[100, 2, 3, 4, 'hello']
>>> immutable = (1, 2, 3, 4)
>>> immutable = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'
(1, 2, 3, 4)
First class functions and High-order functions
Functions being first class means that they can appear anywhere in a program, including return values and arguments of other functions. For example:
print("Hello %s!" % name)
say_hello = greet
# Hello World!
High-order functions are functions which can take functions as arguments (or return them as results). For example, this is what the map()function does, as one of the arguments is the name of the function to apply to a sequence. Notice the difference between the following two lines:
# some_func is an input of other_func() out = other_func(some_func, some_data) # the output of some_func() is the input of other_func() out = other_func(some_func(some_data))
The first case represents a high-order function.
A recursive function calls itself on a smaller input, until the problem is reduced to some base case. For example, the factorial can be calculated in Python as:
if n == 0:
return n * factorial(n-1)
Recursion is also a way to achieve iteration, usually in a more compact and elegant way, again without using internal state representation and without modifying the input data.
While it is possible to write recursive functions in Python, its interpreter doesnt recognise tail recursion, an optimisation technique typically used by functional languages.
Besides the already mentioned map() function, there is more Functional Programming support in the Python standard library.
The functions reduce() and filter() naturally belong together with the map() function. While map will apply a function over a sequence, producing a sequence as output, reduce will apply a function of two arguments cumulatively to the items in a sequence, in order to reduce the sequence to a single value.
In Python 3, reduce() is not a core built-infunction anymore and it has been moved to functools (still part of the standard library), i.e. in order to use it you need to import it as follows:
from functools import reduce
The use of filter() consists in applying a function to each item in a sequence, building a new sequence for those items that return true. For example:
def is_odd(n): return n%2 == 1 print(is_odd(2)) # False print(is_odd(3)) # True values = [1, 2, 3, 4, 5, 6] result = filter(is_odd, values) print(list(result)) # [1, 3, 5]
Notice: the term sequence has to be intended as iterable, and for both map() and filter() the return type is a generator (hence the use of list()to visualise the actual list).
Python supports the definition of lambda functions, i.e. small anonymous functions not necessarily bound to a name, at runtime. The use of anonymous functions is fairly common in Functional Programming, and its easy to see their use related to map/reduce/filter:
words = "The quick brown fox".split() words_len = map(lambda w: len(w), words) print(words) # ['The', 'quick', 'brown', 'fox'] print(list(words_len)) # [3, 5, 5, 3]
Notice: the short lambda function above is just an example, in this case we could use len() directly.
The itertools module groups a number of functions related to iterators and their use/combination. Most of the basic and not-so-basic functionality that you might need to deal with iterators are already implemented there. The official Python documentation has a nice tutorial on Functional Programming, where the itertools module is described in details.
Generator functions and generator expressions in Python provide objects that behave like an iterator (i.e. they can be looped over), without having the full content of the iterable loaded in memory. This concept is linked to lazy-evaluation, i.e. a call-by-need strategy where an item in a sequence is evaluated only when its needed, otherwise its not loaded in memory. This has repercussions on the amount of memory needed to deal with large sequences.
The topic of Functional Programming deserves definitely more than a blog post.
While Functional Programming has its benefits, Python is not a pure functional language. In particular, the mix of mutable and immutable data structures makes it difficult to truly separate the functional aspects of the language from the non-functional ones. The lack of some optimisations (e.g. no tail recursion) also makes some aspects of Functional Programming particularly costly, raising some questions about efficiency.
Even though Python is not openly inspired by other functional language, I think its always important to have an open mind regarding different programming paradigms. From this point of view, knowing what Python can (and cant) do and how can only be beneficial for any Python user.
This work is licensed under a Creative Commons Attribution 4.0 International License.