Side effects and pure functions
Another concept that is important to understand is a side effect. A function has a side effect if it interacts with an external mutable state. A state in this definition can be any kind of store (such as a global variable) or an entity that is accessed via I/O operations (such as a database or a network connection). Side effects exist in several ways:
- A function without an input parameter is a side effect because its action depends on information that has not been provided as an input
- A function without an output parameter is a side effect because it means that it interacted with some external state
- A function that operates on I/O is a side effect because its result depends on an external state, which is either being read or written
The following functions contain side effects:
def side_effect1(i):
print(i)
def side_effect2():
return random(1, 10)
store = 0
def side_effect3(a):
store += a
return store
From its prototype we have the following observations:
- The side_effect1 function is a side effect because it does not return any value. This is confirmed by the fact that it prints a value; that is, does an I/O operation on the console.
- The side_effect2 function can also be directly classified as a side effect from its prototype. It does not contain an input parameter, but returns a value. In this case, the side effect is reading a value from another entity. (By the way, it is also probably an I/O operation on the random device driver, depending on the operating system.)
- The side_effect3 function is a side effect because it stores a value on a global variable. So it interacts with an external state.
So, why are side effects an important thing in functional programming? Identifying side effects is very important because they are parts of code that are non-deterministic. If they are called several times, they do not do the same actions and/or do not return the same values. Since these functions are not deterministic, they cannot be reused easily and they are more difficult to test. This is the reason why side effects must be identified and isolated. However, this does not mean that side effects must be removed. Any useful work by a program is done through side effects (reading or writing to a file, communicating through a network channel, drawing on a screen, reading input from a keyboard or a mouse, and so on). So, the idea is really to isolate and reduce the footprint of side effects so that most of the code of an application is deterministic and more easy to test. Side effects contain necessary behaviors for a program to execute actions.
This means that a functional code contains two kinds of functions: side effects and functions that are not side effects. Any function that does not contain a side effect is a pure function. A pure function is a function whose output depends only on its input. Here are some examples of pure functions:
def addition(a, b):
return a + b
def hello(who):
return "Hello {}".format(who)
Both of these example return a value that depends only on their input parameters. The benefit of pure functions is that they have deterministic behavior. If they are called multiple times with the same parameters as input, they will always return the same value. This behavior is called referential transparency. A referentially transparent function can, in theory, be tested only by calling it multiple times with an exhaustive list of input combinations. Obviously, reality is quite different and exhaustive testing is, most of the time, not possible. Still, pure functions lead to easier testing and fewer bugs.