Funciones de Orden Superior

Tutorial Swift – Funciones de Orden Superior

Josué V. Herrera Swift Avanzado, Tutoriales 0 Comments

En el Tutorial Swift de hoy aprenderemos sobre las funciones de orden superior, muy importantes y de uso frecuente en la programación funcional y en las colecciones de datos.

En la programación funcional a las funciones se les clasifica como ciudadanos de primera clase (first-class citizen) y esto se debe a que son tratadas como cualquier otro objeto, pueden ser asignadas a variables, etc. Debido a esto las funciones pueden también aceptar otras funciones como parámetro al igual que retornar otras funciones, las funciones que describen estas características son llamadas funciones de orden superior. Si esto te suena a funciones como tipos o a closures pues tienes razón, lo único a señalar es que en este tutorial veremos todo esto bajo el ámbito de la programación funcional.

En caso de que no sea del todo aclaratorio lo que acabo de comentar pues no te preocupes, a continuación profundizaremos en el tema, hablaremos de las funciones de orden superior más comunes y como nos pueden ayudar. Como es habitual nos apoyaremos en ejemplos didácticos que vienen acompañados de su respectiva explicación detallada.

Antes de continuar me gustaría remarcar que este tutorial aborda una temática avanzada dentro del lenguaje Swift por lo que se asume el lector está familiarizado con las funciones, el tipo de una función, el concepto de closure y conoce los beneficios de usar un closure. En caso contrario podemos y es siempre recomendado comenzar por aspectos mucho más básicos del lenguaje Swift.

Map

Esta función itera sobre cada elemento de una colección y aplica una misma operación sobre cada una, pero antes veamos como lograr esto de la manera “clásica”:

En este ejemplo tenemos un arreglo de cinco enteros de nombre numberArray al igual que otro vacío llamado emptyArray y donde almacenaremos el resultado de multiplicar cada elemento del primer arreglo por 2, para esto creamos un bucle for en el cual iteramos sobre el arreglo numberArray, donde haciendo uso del método append agregamos a emptyArray el resultado de la multiplicación.

Ahora veamos como lograr lo mismo haciendo uso de map:

En este ejemplo vemos como map forma parte de Array y recibe un closure donde se nos permite a los clientes de esta función definir las operaciones a realizar sobre cada uno de los elementos que tiene mapeado. Dentro del closure hacemos referencia al valor del arreglo y lo multiplicamos por 2. La salida en pantalla es idéntica a la anterior:
Creo que estarán de acuerdo conmigo con que esta versión es mucho más sencilla y limpia que la anterior.

Filter

La función filter itera sobre una colección y retorna un arreglo que contiene elementos que coinciden con cierta condición y de ahí su nombre filtro.

Asumamos ahora que necesitamos filtrar los números pares de un arreglo. Usualmente resolveríamos este problema de la siguiente forma:

…mientras que con filter podemos simplificar el código a este punto:
…la salida en pantalla es:
Nuevamente vemos como haciendo uso de las funciones de orden superior podemos lograr alternativas muchos más legibles que las clásicas.

Reduce

Como las otras dos funciones reduce toma una secuencia como entrada mientras que el producto final es más genérico ya que este puede ser de cualquier tipo.

Con esta función podemos reducir todos los elementos de una colección en un solo valor. ¿Cómo así? Pues sí amigos, por cada iteración del bucle (me refiero al bucle implementado en reduce) reduce pasa el producto de la anterior ciclo en conjunto al elemento actual de la secuencia.

Pero vamos por partes, imaginemos que necesitamos obtener la suma de todos los números miembros de un arreglo de enteros. Una solución sería implementar otro bucle for:

Pero como ya deben de suponer con reduce podemos lograr algo mucho más cool y óptimo:

…obteniendo la misma salida en pantalla:

La función reduce toma dos parámetros, en el primero initialResult es donde especificamos el valor inicial acumulativo, es decir este campo sería el homologo a la variable total del primer ejemplo. El segundo parámetro es un closure de nombre next​Partial​Result el cual combina un valor acumulativo (initialResult) junto con un valor de la secuencia con el objetivo de producir un nuevo valor acumulativo. Este nuevo valor acumulativo será usado a su vez en la próxima llamada y así hasta que se llegue al último elemento de la colección y por último sea retornado como un único valor final, en este ejemplo almacenado en la constante total.

Resumiendo, la función reduce pasará en cada ciclo el resultado parcial acumulado junto al próximo elemento de la secuencia, ambos como argumentos del closure. Tal y como podemos constatar en la siguiente imagen:

Sumario Función Reduce

Digamos que tenemos ahora el código:

En el primer ciclo de iteración sobre este arreglo de número primos se llamará al closure con los parámetros (0, 3) donde “evidentemente” 0 es el valor inicial y 3 es el primer elemento de la secuencia. La suma entonces será 0 + 3 y por ende el valor 3 será almacenado en pos de ser pasado como result. En efecto, en el siguiente ciclo el valor de result es igual a 3 y el elemento actual de la secuencia es 7 por lo que en esta occasion al closure se le llama con los parámetros (3, 7) y así:

Función Reduce

…consecutivamente hasta llegar al último elemento y donde el resultado final se retorna y almacena en este caso en la constante sum.

FlatMap

Digamos que tenemos un arreglo que contiene a su vez dos arreglos más dentro de este y queremos “aplanarlo”, es decir deseamos tener un solo arreglo que contenga los valores de estos dos. Nuevamente un bucle for nos ayudaría con esta tarea:

…pero con flatMap en una línea logramos lo mismo:

…la salida en pantalla es:

Una vez más creo que es evidente el ahorro de tiempo en esta segunda versión del código. Antes de continuar me gustaría puntualizar algo para que no caigan ante la misma confusión en la que se encontró Natasha The Robot al trabajar con flatMap (en Inglés). Pues resulta que cuando especificamos $0 dentro de las llaves de flatMap no estamos haciendo referencia a cada elemento de los array como sucede en map. No, cuando hacemos esto nos referimos a cada arreglo dentro del arreglo padre por lo que si quisiéramos sumar dos a cada entero tendríamos que hacer lo siguiente:

…la nueva salida en pantalla es:

Dicho lo anterior si $0 referencia a un arreglo pues entonces podemos hacer uso de map y es dentro de este donde le sumamos dos unidades a cada elemento, luego cuando flatMap pase al segundo arreglo pues se continuúa con la operación de suma hasta llegar al final.

Otra característica o funcionalidad digna de comentar es aquella donde eliminamos todos los elementos nil del arreglo final, veamos un ejemplo:

…este código es válido (aunque no tenga otros arreglo anidados y creamos que eventualmente intentaremos sumar dos a nil) ya que flatMap obvia el procesamiento del valor nil. Así que:

…la salida en pantalla será de solo 5 elementos.

Prefix

De un arreglo la función prefix nos devuelve la secuencia más larga que satisfaga cierto patrón, comienza por el inicio de la secuencia y se detiene solamente cuando encuentra por primera vez un elemento por el cual el closure especificado nos devuelve el valor booleano false, es decir cuando no cumple con cierta condición.

Veamos un ejemplo sencillo:

En este código comenzamos por crear un arreglo de enteros, seguido a esto creamos otra constante pero con los valores devueltos por la ejecución de un closure sobre la función prefix que a su vez referenciamos en el arreglo de nombre someNumbers.

¿Cuál creen que será la salida en pantalla?

En el closure la expresión evalúa si el elemento es menor (<) que 5, por lo que la función prefix retornará algún valor siempre y cuando esta condición se cumpla desde el inicio del arreglo. La salida en pantalla es la siguiente:

Al llegar al quinto elemento y determinarse que no es menor a 5 pues la expresión dentro del closure devuelve false y la función prefix culmina su ejecución retornando los valor recorridos hasta ese momento.

Si este ejemplo lo modificamos :

…nos devuelve:

En esta versión hemos usado la sintaxis simplificada de los closure que es la que personalmente prefiero. Ahora, el código anterior no retorna valor alguno ya que ninguno de los elementos del arreglo es menos a 0 por lo que la condición jamás es false.

Drop

La función drop es muy similar a prefix, la diferencia reside en que drop se comporta de manera contraria a prefix. En palabras más claras, drop recorre una secuencia de valores dentro de un arreglo de inicio a fin, evalúa sobre estos elementos una condición pasada a través del closure, pero a diferencia de prefix cuando esta condición devuelve false por primera vez su ejecución no termina, es en este punto donde comienza a capturar los valores que luego retornará al llegar al final de la secuencia.

Veamos el anterior ejemplo modificado:

Ahora la condición es que el elemento sea igual a 9 que no está en el arreglo ni tampoco es el primer elemento por lo que ya desde el inicio la expresión es false, así que drop devuelve todos los elementos:
Si cambiamos la condición:
…entonces todos los elementos del inicio del arreglo devolverán true hasta llegar al penúltimo que es 0, la salida ahora sería:
En este último ejemplo hemos hecho lo mismo, pasamos de la sintaxis estándar del closure a usar una más simplificada pero aún legible. Esto lo hago para recordar que los closures tienen una estructura más compleja donde se muestran todos los elementos que lo conforman, creo que es bien importante estar familiarizados con toda la variedad de sus formas. Creo que se ha entendido perfectamente, ¿cierto?

Encadenamiento de Funciones

En este punto es donde realmente las funciones de orden superior realmente destacan. Sí amigos, las podemos encadenar y lograr comportamientos muy interesantes. Digamos que necesitamos calcular la raíz cuadrada de todos los números pares de un arreglo de arreglos:

En la tercera línea hemos utilizado tres funciones de orden superior concatenadas en función y orden de nuestro objetivo. Lo primero que hacemos es llamar a flatMap en pos de unificar los arreglos que hayan en uno solo, seguido a esto filtramos los números pares y por último a estos le calculamos el cuadrado. El nuevo arreglo se almacena en newArray con los siguientes valores:

…valores que pudiéramos sumar, si así fuese necesario, solamente con agregarle reduce:

En este punto si te preguntas que quiere decir (0, +) pues te comento que esta sería la forma más simplificada del closure correspondiente al segundo parámetro. He usado esta variante con intenciones puramente didácticas ya que me luce demasiado críptica y poco legible. Yo prefiero la siguiente variante:

…donde el closure es mucho más explícito en su operación. La salida en pantalla de este código sería:

¿Por qué usar Funciones de Orden Superior?

Los ejemplos que hemos visto son una pista más que clara de los beneficios que nos brindan las funciones de orden superior sobre el uso de los métodos clásicos, con estas escribimos códigos más elegantes y fáciles de mantener que en la mayoría de los casos también se traduce en una mejor legibilidad. Pero creo que el punto más importante sería el habituarnos en el uso de los closures y eventualmente la implementación de nuestras propias funciones de orden superior, esto con el tiempo nos ayudará a leer y a comprender mucho mejor la programación funcional.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

Cualquier sugerencia, ya sea errores a corregir, información o ejemplos a añadir será, más que bienvenida, necesaria!