We already saw in this article that it was possible to store a REFERENCE to a function in a variable. Well, as expected, we can also pass a function as a parameter to another function.
Passing references to functions as parameters to another function simply means that the “receiving” function can accept REFERENCES to other functions as arguments. (of course, there is also the possibility of returning a function via return).
This allows the receiving function to use the function it receives as a parameter. Whether by invoking it directly, storing it for later use, passing it itself to another function, or even doing nothing with it. In short, it can do with it whatever it wants (as if it were any other parameter).
Why would anyone want to pass a function to another? Because it allows for very interesting things. Mainly, it allows us to write more general and reusable functions, as they can work with different behaviors specified by the referenced function.
In short, as a mechanism, it is the bee’s knees foundation for making powerful and reusable tools. It might take you a bit to get used to them, but the effort is worth it. So let’s see their use with a couple of examples.
Use Cases for Function References
One example, imagine you have a function that iterates through the elements of a collection. Furthermore, the way to iterate is complex, so you want to reuse that code as much as possible.
Now you want to use your function to iterate through the collection and execute an action on the elements that meet a criterion. We can reuse your iterate_collection function if we also pass it the criterion function and the action function to perform.
function recorre_coleccion(criterio, action)
{
// do things
}
Another example, imagine a function has to perform a very long and complicated process. Furthermore, this process can succeed 👍, or fail ❌.
You can make a do_something_very_complicated function that does the process. To this, you pass two functions. One for success that will be executed if everything goes well, and one for error if something goes wrong.
function haz_algo_muy_complicado(exito, error)
{
// do something very complicated
// with possible calls to error()
// and if everything goes well, success
exito();
}
What do these two examples have in common? That passing functions as parameters has allowed us to reuse the code of a very complicated function. We have added versatility to it, and we can use it in many more cases.
Examples of Functions as Parameters in Different Languages
Let’s see how the use of Higher-Order Functions and passing functions as parameters would be done in different programming languages.
In this example, we are going to create two functions Add and Subtract, which receive two parameters a and b and perform the appropriate operation.
On the other hand, we are going to have a higher-order function called ExecuteOperation. This function receives two parameters a and b, and the operation to execute.
Finally, we invoke ExecuteOperation with different a and b parameters, and the operation to perform (Add or Subtract).
// Delegate to represent a function that takes two integers and returns an integer
delegate int Operacion(int a, int b);
// Function to add two numbers
int Sumar(int a, int b)
{
return a + b;
}
// Function to subtract two numbers
int Restar(int a, int b)
{
return a - b;
}
// Function to execute a mathematical operation
int EjecutarOperacion(int a, int b, Operacion operacion)
{
return operacion(a, b);
}
Console.WriteLine(EjecutarOperacion(5, 3, Sumar)); // Prints: 8
Console.WriteLine(EjecutarOperacion(5, 3, Restar)); // Prints: 2
// Function pointer type declaration that takes two integers and returns an integer
typedef int (*Operacion)(int, int);
// Function to add two numbers
int Sumar(int a, int b)
{
return a + b;
}
// Function to subtract two numbers
int Restar(int a, int b)
{
return a - b;
}
// Function to execute a mathematical operation
int EjecutarOperacion(int a, int b, Operacion operacion)
{
return operacion(a, b);
}
int main()
{
// Use function references to perform operations
std::cout << EjecutarOperacion(5, 3, Sumar) << std::endl; // Prints: 8
std::cout << EjecutarOperacion(5, 3, Restar) << std::endl; // Prints: 2
return 0;
}
// Function to add two numbers
function sumar(a, b) {
return a + b;
}
// Function to subtract two numbers
function restar(a, b) {
return a - b;
}
// Function to execute a mathematical operation
function ejecutarOperacion(a, b, operacion) {
return operacion(a, b);
}
// Use function references to perform operations
console.log(ejecutarOperacion(5, 3, sumar)); // Prints: 8
console.log(ejecutarOperacion(5, 3, restar)); // Prints: 2
# Function to multiply two numbers
def multiplicar(a, b):
return a * b
# Function to divide two numbers
def dividir(a, b):
return a / b
# Function to execute a mathematical operation
def ejecutar_operacion(a, b, operacion):
return operacion(a, b)
# Use function references to perform operations
print(ejecutar_operacion(5, 3, multiplicar)) # Prints: 15
print(ejecutar_operacion(5, 3, dividir)) # Prints: 1.6666666666666667
Higher-Order Functions
Functions that accept other functions as arguments or return functions as results are known as higher-order functions.
That is, a Higher-Order Function is one that,
- Can receive one or more functions as arguments
- Can return a function as a result (via
return).
It’s a term that has become popular because it has an important (mostly theoretical) role in functional programming. And functional programming is “in fashion”.
// "super cool" function that receives another function
function FuncionDeOrdenSuperior(function otraFuncion)
{
}
// poor common function that only receives numbers
function FuncionNormal(int numero)
{
}
But don’t give it more importance than it deserves. At its core, they are nothing more than ordinary functions, just like the others.
Simply put, some of the data types they handle are References to functions. (but there’s an impressive snobbery about naming everything 😄).
Relationship with Lambda Functions
Lambda functions (also known as anonymous functions) are a concise way to define functions inline without the need to assign them a name.
Lambda functions are especially useful when you need a function that you will only use once, especially if it’s short. Here it’s not worth creating a “normal” function, and lambda functions make your life easier.
So on one hand we have a syntax for short functions, and on the other, functions that accept other functions. You see where I’m going, right? They are concepts that fit together very well.
function ExecuteAction(action)
{
// do whatever
}
ExecuteAction(() => console.log("hello")); // look how convenient 😉
The combination of lambda functions and passing function references as parameters is a common and very frequent practice in many modern programming languages.
Internal Operation Advanced
The internal operation of passing and returning functions from a function is very simple, if you have understood the concept of a REFERENCE to a function.
Let’s remember that a reference to a function is simply a variable that contains the address of a function, instead of data, or the address of another variable.
Apart from that, for the receiving function, the management is exactly the same as with any other type of variable. It receives it, returns it, copies it, stores it… the function basically doesn’t care.
// this function doesn't care if something is 10, "potato" or a function
function FuncionDeOrdenASaber(algo)
{
}
The difference will be in how the variable is used. And, if the language is typed, in the check it performs to ensure the types are correct.
function FuncionDeOrdenASaber(algo)
{
algo(); // here is the difference, when using the variable
}
// here is another difference, when passing the variable
FuncionDeOrdenASaber(() => console.log("hello"));
But in reality, there is no difference in terms of the operation of a Reference to a function compared to any other type of variable.
