Most of the functions we have written so far are stateless. That means if you pass in the same inputs, you always get the same outputs. Also, they do not do anything else apart from returning the value.
def add(a, b): return a + b add(2, 3) # 5 add(2, 3) # 5 add(2, 3) # 5
Such functions are called pure functions or stateless functions or side-effect free functions. The output of the function depends only upon the input. If the input is the same, the output will also be the same.
Most of the time, functions are stateless. But you can also create stateful functions. The code below defines a higher order function called
def once(fn): called = False def inner(*args, **kwargs): nonlocal called if not called: called = True return fn(*args, **kwargs) return inner once_add = once(add) once_add(2, 3) # 5 once_add(2, 3) # None
once a function as input, and it returns the function
inner as output. When you call the returned function, it calls the underlying function and returns the result. In the example above, calling
once_add(2, 3) will call the underlying function
add and return
5 as the answer. But if you call it again, it will return
In other words, the
once function takes a normal function as input and converts it to a version that can be called only once.
Notice here that
once_add gives different results for the same input. Thus,
once_add is a stateful function or an impure function. The output of this function does not depend only on the inputs
(2, 3) but also the hidden state variable
called. This hidden state variable is not a part of the inputs. Hence the function is impure.
Unlike pure functions, we cannot reason easily about impure functions. For example, what will be the output in the below two cases?
add(2, 3) # always 5 once_add(2, 3) # ?
In the first case, the output is always 5. But in the second case we cannot tell what the output will be. It all depends on whether
once_add has been called beforehand or not. We need to walk through all the previous code and see what has happened previously in order to figure out what will happen here. This is the main problem with stateful functions.
However, stateful functions can also make code simpler to read by encapsulating the state. If there is not much code between creating and using
once_add so that it is clear when it is called once or twice, then the stateful version can be easier to read.
Another form of an impure function is side-effecting functions. Take a look at the
add function below
def add(a, b): print("adding") return a + b
Apart from returning the sum, this function also prints a message to the terminal (this act of printing is called a side-effect as it is something that happens apart from the return value). What is the problem with side effects?
Well, assume that a programmer sees the code below
if user == "y": val = add(2, 3) val = add(2, 3) print(val)
The programmer, seeing the code above wonders why
add has been called twice? Why call it inside the condition and then again do the same thing immediately outside? The programmer decides to simplify the code
val = add(2, 3) print(val)
Do you see the bug introduced with this code change?
In the first program, if
user == "y" then it will print the message
"adding" twice. Once on line 2 and again on line 3. But after the simplification, the message will be printed only once. The two pieces of code are not equivalent and they give different outputs, because of the side-effect behaviour of
The programmer needs to understand the implementation of
add before being confident of this code change. Whenever you need to know the implementation of a function before using it, then it breaks abstraction and makes the program more difficult to understand and maintain.
This is the fundamental problem with side effects. With pure functions you can always cache the outputs and avoid calling the function again etc. But when side effects are involved you cannot do this.
Still, operations like printing to the screen, geting UI events, reading and writing files, sending data over the network are all required in any program. Unless you are working on toy programs, you will always need side effecting functions.
Pure functions are easy to reason about. They don't do any funny stuff. They are easy for a programmer to read and understand. They can be cached, or run in parallel without introducing any bugs.
Stateful functions and side-effecting functions have problems, but they are also essential to any bigger program. The key to writing maintainable programs is to use these judiciously in the right places.
Many functional programming languages allow you to code stateful functions to some extent or the other. However, Haskell is a very pure functional programming language which completely disallows stateful functions. Even the state needs to be passed into the function as a parameter.
Did you like this article?
If you liked this article, consider subscribing to this site. Subscribing is free.
Why subscribe? Here are three reasons:
- You will get every new article as an email in your inbox, so you never miss an article
- You will be able to comment on all the posts, ask questions, etc
- Once in a while, I will be posting conference talk slides, longer form articles (such as this one), and other content as subscriber-only