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

30/05/2018Artículo original

(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 variablevar 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}!"endgreet2 = -> 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.

  Microsoft lanza una nueva fuente de código abierto para programadores que además de bonita es gratuita

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 varianzastandard_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.

  Microsoft ha lanzado 44 vídeos en inglés para principiantes para aprender a programar con 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 izquierdaconst 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.

  Ya podemos descargarnos el framework .NET 5.0, disponible por primera vez para Windows ARM64 y WebAssembly

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.

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad