Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=df5e8647-94e9-4374-8a9b-a191c24e8c3e

Programación funcional: Funciones de primera clase y de orden superior

(Fotografía de base de la Nave clase Lambda  de Lego por Ben Rollman, CC BY-NC-SA 2.0)

Hoy vamos a conocer otra de las claves que hace a la programación funcional una herramienta verdaderamente potente, que permite simplificar mucho el código y que cada vez llega a más lenguajes: el soporte de funciones de primera clase y las funciones de orden superior. Como siempre, trataré de llegar a todo tipo de programadores, especialmente los que no trabajan frecuentemente con lenguajes funcionales, de forma que veremos ejemplos de aplicación en distintos lenguajes no funcionales.

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);
  };
};

En este ejemplo he usado algunas características modernas de JavaScript: template strings y funciones flecha. Si tienes interés en aprender más tal vez quieras consultar el curso sobre Programacion avanzada en JavaScript y ECMAScript .

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 bind como 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

Relación

Los lenguajes que tienen funciones de primera clase ya proporcionan la funcionalidad suficiente para tener funciones de orden superior; sin embargo, un lenguaje con funciones de orden superior no necesariamente tiene funciones de primera clase.

Por ejemplo, en Ruby hemos visto que las funciones se deben tratar de forma especial para poder pasarlas como parámetro o almacenarlas en una variable, así que no son objetos de primera clase. Además, otros lenguajes proporcionan sólo parte de la funcionalidad, puedes consultar el soporte en esta tabla.

En este artículo hemos aprendido a aprovechar características funcionales disponibles en gran parte de los lenguajes de programación, espero que te sirva para facilitarte el trabajo de ahora en adelante.

David Charte David Charte es un ingeniero informático y matemático apasionado por la divulgación del conocimiento. Cuando no está tratando de aprender un nuevo lenguaje, investiga en el campo de la ciencia de datos. Puedes seguirlo en Twitter en @fdavidcl. Ver todos los posts de David Charte

No te pierdas ningún post

Únete gratis a nuestro canal en Telegram y te avisaremos en el momento en el que publiquemos uno nuevo.

Archivado en: Lenguajes y plataformas

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.