Many times reading articles on the Internet, you come across this term: function is a "first class citizen" in some language. This might have been in reference to a functional programming language or maybe even Python. In this article, we will take a look at what this term means, and what it means for our Python code.
Later on in the series we will see how to take advantage of this behaviour in practice. We will also talk about higher order functions, decorators, and work through a couple of sample kata problems. Finally, we will wrap up with function composition and monads (yes!).
So, lets get on with the topic
Code and data
When we are first taught programming, we first come across two concepts. You have your data, like numbers, strings, objects and so on. And you have code, which contains instructions on how to operate on the data.
def add(a, b):
return a + b
In the snippet above, the add
function takes two numbers (data) and then does an operation (+) and returns the answer. The add
function itself is the code, while a
and b
are data.
Code and data, these are the two fundamental concepts of programming. In this categorisation, the data that you can manipulate or do operations on are the "first class citizens". You can create them at runtime, have variables point to them, pass them to functions, do operations on them, return then from functions etc.
In some languages, the separation between code and data is strict. You can manipulate data at runtime, but you cannot manipulate the code at runtime. In this case, code is a "second class citizen" as – unlike data – it cannot be manipulated at runtime.
In languages like C, you can have function pointers which point to functions, and you can pass these function pointers around, making it possible to execute different functions at runtime based on what the function pointer is pointing to.
int add(int a, int b) {
return a + b;
}
int mul(int a, int b) {
return a * b'
}
int do_operation(int (*fun)(int, int)) {
return (*fun)(2, 3);
}
do_operation(&add); // returns 5
do_operation(&mul); // returns 6
Here I can make do_operation
do an add
or a mul
depending on which function pointer I pass it.
However, you cannot create new functions at runtime. Therefore, in C, functions have some properties of all the other "normal" data types, but not everything, and functions are "second class citizens".
Functions as first class citizens
In languages where functions are first class citizens, they have all the properties as any other data type. You can pass them as parameters (like in the C example above), but you can also return them from functions, and you can create new functions at runtime. Thus, functions are equal with any other data type on what you can are allowed to do with them. In other words, a function
is a full data type by itself.
def add(a, b):
return a + b
print(type(add)) # <class 'function'>
Having a function as a first class citizen opens up many new ways of programming. For example, we can assign a function to a variable
def add(a, b):
return a + b
my_var = add
my_var(2, 3) # 5
Here we assigned the add
function to the my_var
variable. Then we use the my_var
variable to execute the function. It executes 2 + 3
and gives 5
as the result.
In fact, add
itself is just a variable in python. Python actually creates a function object in memory and sets the name of the function as a variable to point to the function object. When we do my_var = add
then it just sets another variable to point to the same function object. So whether we execute add(2, 3)
or my_var(2, 3)
it ends up executing the same code. You can see it visually in the picture below
In fact, you can even create a function without a name. That's what lambda
does
add = lambda a, b: a + b
add(2, 3) # 5
Here the function object is created in memory and then add
is assigned to that object. Internally there is no difference between the two code snippets below (though lambda
has a limitation that it can only be used to return the output from a single expression, but that is a language design choice. Conceptually they are the same)
def add(a, b):
return a + b
add = lambda a, b: a + b
What we see here is that python allows us to create new functions at runtime. Until the point add = lambda a, b: a + b
is executed this function does not exist at all. When that line is executed, Python creates that function in memory and then we can use it after that.
So far we have seen how to create new functions at runtime and how to assign variables to a function. Can we pass in a function as a parameter?
def add(a, b):
return a + b
def mul(a, b):
return a * b
def do_operation(fun):
return fun(2, 3)
do_operation(add) # 5
do_operation(mul) # 6
This code is the equivalent of the C code posted above. You can pass in a function to do_operation
and that function will get executed with the parameters (2, 3)
What about returning a function from a function? Try this code
def make_fn(val):
return lambda: val
val_2 = make_fn(2)
val_2() # 2
val_5 = make_fn(5)
val_5() # 5
Here we have a function that takes a number. It then returns a new function object that returns that number. Note that everytime make_fn
is called, it creates a new function in memory and returns it.
Summary
In this article we have seen how functions in Python are first class citizens. You can do all the things that are possible with other data types: Creating new instances at runtime, assigning variables to them, passing them to other functions and even writing functions that create and return new functions. In the next article, we will take a look at some of the things we can do with this property.
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