Lambdas and functional programming
We can define lambda functions as anonymous functions. More informally, they are quick functions to define if you are too lazy to define a normal one. They can be declared in one line and do not even need to be assigned to a variable. This is a lambda function.
lambda a, b: a + b
It contains two arguments, a
and b
, and returns their sum. It is optional, but you can assign it to a variable to name it.
add = lambda a, b: a + b
Once we have the function, it is possible to call it as if it were a normal function. As you can see, they are equivalent to the “normal” functions we have seen before.
add(2, 4)
Lambda functions are closely related to functional programming. This is a programming paradigm where everything revolves around functions. It is often said that these are first-class citizens.
In functional programming, it is common to use the following primitives, which are applied to iterables such as lists:
- 🔧
map
: Modifies each element. - 🧲
filter
: Filters the elements that meet a condition. - 🌪️
reduce
: Accumulates all the elements, reducing them to a value with a given logic.
Let’s see these three functions in action combined with lambda
functions. Imagine we have the following list.
nums = [1, 2, 3, 4, 5]
🔧 With map
, we can modify each element using a lambda
function. We modify each element by squaring it.
But there is a trick to it. What is returned is not the modified list. It is an iterator.
print(map(lambda x: x**2, nums))
# <map object at 0x1046ff940>
Since it is an iterator, you can iterate over it in the following way. This is closely related to generators and the concept of lazy seen earlier.
for i in map(lambda x: x**2, nums):
print(i)
# 1, 4, 9, 16, 25
If you want to get the list, you can convert the iterator to list
.
nums_squared = list(map(lambda x: x**2, nums))
print(nums_squared)
# [1, 4, 9, 16, 25]
So far, nothing new. You can get the same result with the same old loops.
nums_squared = []
for x in nums:
nums_squared.append(x**2)
print(nums_squared)
# [1, 4, 9, 16, 25]
🧲 With filter
, we can filter the values. For example, we keep the even numbers.
nums_even = list(filter(lambda x: x % 2 == 0, nums))
print(nums_even)
# [2, 4]
Which is equivalent to doing the following.
nums_even = []
for x in nums:
if x % 2 == 0:
nums_even.append(x)
print(nums_even)
# [2, 4]
🌪️ And with reduce
, we can accumulate all values into one. For example, we add them all together.
from functools import reduce
total = reduce(lambda x, y: x + y, nums)
print(total)
# 15
Which is equivalent to this.
total = 0
for x in nums:
total += x
print(total)
# 15
As you can see, map
, filter
, and reduce
allow us to do things we already knew how to do but in a different way. It avoids loops and is closer to how it would be done in functional programming.
There is also a small nuance. Both map
and filter
are lazy, which means that they return an iterator that computes values as needed, applying our function to each element. This consumes very little memory.
The case of reduce
is different since it uses eager evaluation. That is, it calculates the resulting value.
Although we have previously used map
with lambda functions, we can also use normal functions. Although they require a few more lines of code.
def square(x):
return x ** 2
nums_squared = list(map(square, nums))
print(nums_squared)
# [1, 4, 9, 16, 25]
Finally, let’s look at a practical example. We have a list of people with different attributes.
people = [
{'Name': 'Alice', 'Age': 22, 'Gender': 'F'},
{'Name': 'Bob', 'Age': 25, 'Gender': 'M'},
{'Name': 'Charlie', 'Age': 33, 'Gender': 'M'},
{'Name': 'Diana', 'Age': 15, 'Gender': 'F'},
{'Name': 'Esteban', 'Age': 30, 'Gender': 'M'},
]
We can keep everyone’s names.
names = list(map(lambda p: p['Name'], people))
print(names)
# ['Alice', 'Bob', 'Charlie', 'Diana', 'Esteban']
Or we can filter out those over 32
years old.
seniors_32 = list(filter(lambda p: p['Age'] > 32, people))
print(seniors_32)
# [{'Name': 'Charlie', 'Age': 33, 'Gender': 'M'}]
We can also sort by age and keep the names. We can see that Diana
is now first.
sorted_names = list(
map(lambda x: x["Name"],
sorted(people, key=lambda p: p['Age'])))
print(sorted_names)
# ['Diana', 'Alice', 'Bob', 'Esteban', 'Charlie']
This is one of the interesting things about Python. It is a multi-paradigm programming language, which means that we can do the same thing in very different ways. In this section, we have seen how to do it in a more functional way.