First class functions

Functions are "first class citizens" in python. In this article we will take a look at what that means.

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

Calling add() or my_var() both execute the same function object in memory

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.

Comments

Sign in or become a Playful Python member to join the conversation.
Just enter your email below to get a log in link.