Funciones de primera clase
Se dice que en un lenguaje las funciones son de primera clase (o que son "objetos de primera clase") cuando se pueden tratar como cualquier otro valor del lenguaje, es decir, cuando se pueden almacenar en variables, pasar como parámetro y devolver desde funciones, sin ningún tratamiento especial. El ejemplo más claro en un lenguaje popular lo encontramos en JavaScript, donde estas operaciones con funciones son muy comunes:
// Asignación a variable
var get_post = function(post_number) {
return fetch(`https://example.org/posts/${post_number}`)
// Paso como parámetro
.then(response => response.json())
.then(function(data) {
console.log(data);
});
};
var custom_exp = function(base) {
// Valor de retorno
return function(exponent) {
return Math.pow(base, exponent);
};
};
Como podemos ver, en JavaScript es natural utilizar funciones como parámetros, valores de retorno y en variables. Esto también es así en otros lenguajes como R o Scala. Muchos otros introducen un tipo de funciones denominadas lambdas (o funciones anónimas), que en ocasiones se comportan de forma distinta a las funciones usuales para permitir esas operaciones. Por ejemplo, en Ruby las lambdas son objetos pero no así el resto de funciones:
def greet who = "Mundo"
"¡Hola #{who}!"
end
greet2 = -> who { "¡Hola #{who}!" }
greet # => "¡Hola Mundo!"
greet2 # => #<Proc:... (lambda)>
En el ejemplo anterior, greet no hace referencia a la función sino que la ejecuta, devolviendo el mensaje "¡Hola Mundo!", mientras que greet2 sí nos indica que es un objeto de tipo Proc, el cual se puede pasar como argumento, devolver, etc.
Una aplicación interesante de la propiedad de funciones de primera clase es escribir versiones parcialmente aplicadas de otras funciones. Por ejemplo, supongamos que queremos evaluar la función de densidad de una distribución normal para una media y desviación dadas. Podríamos escribir nuestra función de la siguiente manera:
function gaussian(mean, variance, x) {
return 1 / Math.sqrt(2 * Math.PI * variance) *
Math.exp((x - mean)**2 / (-2 * variance));
}
Esta implementación nos impide reutilizar la media y la varianza de la distribución para evaluar en varios puntos sin escribir de nuevo los parámetros. En su lugar, consideremos la siguiente versión:
function gaussian_alt(mean, variance) {
return function(x) {
return 1 / Math.sqrt(2 * Math.PI * variance) *
Math.exp((x - mean)**2 / (-2 * variance));
};
}
var standard_normal = gaussian_alt(0, 1);
console.log(`N(3 | 0, 1) = ${standard_normal(3)}`);
Ahora podemos reutilizar la standard_normal tanto como queramos. Esto es aplicable a muchas otras situaciones donde conviene que nuestras funciones estén parametrizadas a varios niveles y podamos proporcionar los argumentos poco a poco. En ocasiones, el lenguaje proporciona la funcionalidad necesaria para obtener dichas versiones parcialmente aplicadas sin necesidad de reescribir la función:
// Aplicamos la media y varianza
standard_normal = gaussian.bind(null, 2, 1);
console.log(`N(3 | 0, 1) = ${standard_normal(3)}`);
La sintaxis para la aplicación parcial de funciones suele diferir entre lenguajes: en JavaScript usamos bindcomo en el ejemplo, en C++ está disponible std::bind, en Python functools.partial...
Funciones de orden superior
Cuando una función no recibe otras funciones como parámetro, se la denomina de primer orden. En el caso en el que sí las reciba, se llama de orden superior.
Muchos lenguajes nos proveen con una serie de funciones de orden superior para trabajar con estructuras de datos. De entre ellas, las más conocidas son map y reduce: la primera aplica la misma función sobre cada elemento de una colección, y la segunda acumula los elementos en un único valor según una función dada. Veamos un ejemplo:
const list = [1, 2, 3, 4, 5];
const squared = list.map(x => x ** 2);
// => [1, 4, 9, 16, 25]
const product = list.reduce((acc, item) => acc * item, 1);
// => 120
Es importante notar que map no modifica la colección original sino que devuelve una nueva, esto se verifica también en la mayoría de lenguajes que la proporcionan. También es de uso común una función filter, que seleccione elementos mediante un predicado booleano:
const even = list.filter(x => x % 2 == 0);
// => [2, 4]
Casos interesantes de uso de funciones de orden superior son el módulo Enumerable de Ruby, los métodos de la interfaz Stream de Java y los decoradores de Python.
Una última función de orden superior que resulta muy obvia pero no siempre viene integrada en los lenguajes es la composición de funciones. En JavaScript, por ejemplo, podríamos implementar la composición de dos funciones como sigue:
const comp2 = (f, g) => ((...arg) => g(f(...arg)));
const abs = comp2(x => x * x, Math.sqrt);
abs(-4); // => 4
Para componer más de dos funciones, podemos componerlas dos a dos aprovechando el ejemplo anterior y una variante de la función reduce que acabamos de aprender:
const compose = function(...functions) {
return functions.reduceRight(comp2);
};
// Las funciones se aplican de derecha a izquierda
const f = compose(Math.floor, Math.log, Math.max)
f(10, 20) // => 2