1. Introduction
In this tutorial, we’ll discuss lambda functions and their use in programming.
2. Where Does “Lambda” Come From?
Anonymous functions, lambda expressions, or function literals are all the same thing. Lambda (or \lambda) is the name given to anonymous functions in some languages like Python. These are functions not bound by an explicit identifier. This name is derived from Lambda calculus, a mathematical system to express computation introduced by Dr. Alonzo Church in the 1930s.
Dr. Church’s incredible work was aimed at exploring the foundations of mathematics and the reduction of complex functions into simple 1-argument “lambda” expressions. These are small functions with no name that are used to make bigger functions through a process called currying. Currying means that multi-argument functions get broken down into simple 1-argument functions.
This means complex functions can be torn apart into anonymized bite-size chunks.
Furthermore, creating simple anonymous functions is convenient when you don’t necessarily want to define a full function for a very simple task.
3. Basic Implementation
In programming, there are many applications for this, but the biggest are sorting, closures and currying. For ease of reading, I will be implementing the following code in Python, JavaScript, and C++.
The syntax is fairly simple for a lambda function.
Here are a few examples in Python:
lambda x : x+1
lambda x,y : x+y
We can observe that the word “lambda” symbolizes the anonymous function. What follows before the colon are the variables needed by the function. What comes after the colon is the logic of the function.
Things are a little different in JavaScript:
//Traditional Anonymous Function
function(a){
return a + 1;
}
This is an anonymous function. It cannot be named. Because of this, for most applications in JavaScript, we want to use the arrow “=>” function instead:
// we can define them anonymously:
a => a + 1;
// or issue a name:
let myfunction = a => a+1;
This arrow function can be named and more closely resembles the lambda function implemented in Python.
We can see that to the left of the arrow, there are the variables that will be used and to the right exists the logic that will be implemented.
Moving on to C++, we can see that the arrow notation persists (at least since C++ 11). We can see that they hold the following general structure:
[ capture clause ] (parameters) -> return-type
{
method
}
We can make an example function just like the ones earlier:
[](double a) -> double {return a + 1}
Of course, if we want to access other variables, we can specify them in the capture clause like so:
[variable](double a) -> double {
if (a < variable) {
return 0;
} else {
return a;
}
}
These capture clauses are interesting in C++ as we will often see lambda functions can be nested inside larger functions.
4. Sorting
**Often, we aim to sort items in a list by a specific logic. If the logic is simple, we can use a lambda function to achieve this.
**
Let’s say that we want to sort a list by the length of each entry in Python:
a= ['somethingevenlonger', 'somethinglonger', 'something']
a.sort(key=lambda x : len(x))
print(a)
The output of this is:
['something', 'somethinglonger', 'somethingevenlonger']
In this example, we can see that our lambda function is:
lambda x : len(x)
This function returns the length of the argument “x”.
The sort method uses this lambda function as a key.
**Similarly, in JavaScript, we can sort strings using the arrow function:
**
const arr = [1,2,3,4,5,6]
function sortNums(arr) {
let sorted = arr.sort((a, b) => b - a);
console.log(sorted.join(", "));
}
// Log to console
console.log(arr)
sortNums(arr)
The output for the following code is the following:
[1 ,2 ,3 ,4 ,5 ,6]
6, 5, 4, 3, 2, 1
Using C++, we can obtain the same result:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
int main() {
auto vectors = std::vector<int> { 7, 5, 16, 8 };
auto Lambda = [] (int s1, int s2) -> bool {
return s1 < s2;
};
std::sort(vectors.begin(), vectors.end(), Lambda);
for (auto v : vectors)
std::cout<<v<<'.';
}
Note that the lambda function used here is called Lambda and takes two integer arguments. It returns a “True” or “False” boolean statement based on the comparison between the size of its arguments.
We can see how the terminal prints the values in order:
5.7.8.16.
5. Closures
Sometimes the term closure is used interchangeably with the terms “lambda function” or “anonymous function”. However, closures are actually instances of functions with non-local variables assigned. We can think of these as anonymous objects that can collect variables in scope.
We can demonstrate this using the following example in Python:
def a(x):
def b(y):
return x + y
return b # Returns a closure.
We can see that b requires the x variable from the a(x) method. Because it is not defined inside b, it’ll use the x on the previous scope (a).
Alternatively, we could use lambda functions to achieve the same result:
def c(x):
return lambda y: x + y # Return a closure.
Both of these produce mathematically identical results:
result1 = a(4)
print(result1(4))
result2 = c(4)
print (result2(4))
# Both of these return 8
In JavaScript, we can also implement closures:
function wrapper(x) {
let f1 = (x) => {x=x+1;console.log(x);}
return f1;
}
let x = 0;
let lambda = ()=>{x=x+1;console.log(x);}
lambda();
lambda();
lambda();
let w=wrapper();
w(x);
w(x);
w(x);
console.log(x);
This script outputs the following:
1
2
3
4
4
4
3
We can see that calling the wrapper function is not actually modifying the value of the variable x. This is because it is modifying its own local variable x.
Below is another C++ implementation of the concept:
#include <iostream>
#include <functional>
std::function<void(void)> WrapClosure() {
int x = 1;
return [x](){std::cout << x << std::endl;};
}
int main() {
int x = 1;
auto lambda = [&x](){
x += 1;
std::cout << x << std::endl;
};
std::function<void(void)> wrapper = WrapClosure();
lambda();
lambda();
lambda();
std::cout << "x is equal to "<<x<<std::endl;
wrapper();
wrapper();
wrapper();
std::cout << "x is equal to "<<x<<std::endl;
}
The output is the following:
2
3
4
x is now equal to 4
1
1
1
x is now equal to 4
We can see that the lambda contains the address (&) from the x variable used in the larger main function scope. This is important to note when using closures. Simply using x wouldn’t modify the x variable in the main function due to the limited scope of the lambda.
6. Currying
Currying is the process of taking complex functions and turning them into a series of 1-argument functions. This has many advantages. The most evident one is that functions can be modified, and different functions can evolve from the same code.
Let’s say that we are trying to multiply two numbers together in Python:
def multiply(x,y):
return x*y
def multiplier(x):
return lambda y: x*y
So far, both of these functions do the same thing. But with the second function, we can create additional functions:
twice = multiplier(2)
tentimes = multiplier(10)
These functions can now be called and re-used for other purposes. Calling these functions would look like this:
twice(4)
tentimes(4)
This is the same as using the code below:
multiply(4,2)
multiply(4,10)
Using JavaScript, we can also implement this concept:
function multiply(x,y) {
return x*y;
}
function multiplier(x) {
return (y)=>x*y;
}
let timesfour = multiplier(4);
let timesfive = multiplier(5);
console.log(timesfour(2));
console.log(multiply(4,2));
console.log(timesfive(5));
console.log(multiply(5,5));
We can confirm the outputs:
8
8
25
25
The same is true in C++:
#include <iostream>
#include <functional>
void multiply(int x,int y){
std::cout<<x*y<<std::endl;
}
auto multiplier(int x){
return [x](int y){std::cout<<x*y;};
}
int main(){
multiply(2,3);
auto timestwo = multiplier(2);
timestwo(3);
}
Here, both timestwo(3) and multiplier(2,3) output the same value: 6.
7. Filtering
Python has a commonly used function called filter. This function allows parsing through values using a boolean logical expression. It has the following syntax:
filter(object, iterable)
The first argument in the function can be a lambda function. The second has to be iterable, and the easiest example of this is probably a list.
This is often used to process lists that need to display “all items less than X” or “all items equal to Y”.
The code below will demonstrate the use of the first of these list operations:
iterable = [1,2,3,4,6]
a = filter(lambda x : x<3, iterable)
print(list(a))
Similarly, in JavaScript, we have :
iterable=[1,3,4,5,6]
console.log(iterable.filter(element => element > 4));
This outputs:
[ 5, 6 ]
And finally, using C++, we can make our own filter function like so:
#include <iostream>
#include <algorithm>
#include <vector>
int main () {
std::vector<int> iterable = {1,2,3,5,15};
std::vector<int> result (iterable.size());
// copy only positive numbers:
auto it = std::copy_if (iterable.begin(), iterable.end(), result.begin(), [](int i){return !(i>4);} );
result.resize(std::distance(result.begin(),it)); // shrink container to new size
std::cout << "result contains:";
for (int& x: result) std::cout << ' ' << x;
return 0;
}
This outputs the following:
result contains: 1 2 3
8. Mapping
Mapping is similar to the filter function, but instead of using a boolean expression to filter values, this one actually changes the values in place.
Another interesting tool to do this in Python is the map function.
The syntax is as follows:
map(object, iterable_1, iterable_2, ...)
This means that we can use the same object for multiple iterables.
This can come in handy when we want to apply the same transformation to all the items in one or more iterables:
numbers_list = [1, 1, 1, 1, 1, 1, 1]
mapped_list = list(map(lambda a: a + 1, numbers_list))
print(mapped_list)
The output of the following code is:
[2, 2, 2, 2, 2, 2, 2]
As we can see, it added “1” to all the items in the list.
We can use JavaScript arrow functions to achieve similar behavior using the map method for arrays:
iterable=[1,3,4,5,6]
console.log(iterable.map(element => element + 4));
This adds 4 to every number inside our array and returns:
[ 5, 7, 8, 9, 10 ]
In C++, we can also implement this:
#include <iostream>
#include <algorithm>
#include <vector>
int main () {
std::vector< int > arr = { 1,2,3,4,5,6,7,8,9 };
std::for_each( arr.begin(), arr.end(),
[](int & x)
{ //^^^ take argument by reference
x += 2;
std::cout<<x<<'.';
});
return 0;
}
We can see that we are adding 2 to each item in our vector. This is what the output looks like:
3.4.5.6.7.8.9.10.11.
9. Conclusion
In this article, we discussed lambda functions and their use in programming.