Funciones de primer orden II

Archivada en Desarrollo

Funciones de primer orden II

Creo que es muy importante comprender correctamente qué son y el papel de las funciones de primer orden en la programación funcional. Vamos a seguir revisando este paradigma de programación con funciones cada vez más concretas y con aplicación en el día a día.

La función todos

Es frecuente que, cuando programamos, tengamos que verificar si un arreglo contiene números, un objeto o alguna otra cosa (pensemos en los requisitos de las metas). Lo que haríamos sería un ciclo for para resolver este problema. Pero también podemos hacer una abstracción usando nuestra función todos. La función todos recibe dos argumentos: una arreglo y una función. Lo que hace es verificar si todos los elementos del arreglo se evalúan como true por la función que le pasamos como argumento. La implementación se ve así:

const _todos = (arr, fn) => {
  let resultado = true
  for (let i = 0; i < arr.length; i++)
    resultado = resultado && (fn(arr[i]))
}

Simplemente es un ciclo que recorre todo el arreglo y llama a la función fn con cada valor. Ahora bien, la función fn debe regresar un valor Booleano, porque con este hacemos una prueba lógica && para asegurar que el contenido del arreglo obedece al criterio dado por fn.

Para verificar el funcionamiento de nuestra función, vamos a pasarle un arreglo de valores NaN y la función isNaN() como argumento para que verifique si el número dado es NaN o no.

consola(todos([NaN, NaN, NaN], isNaN))
> true
consola(todos([NaN, NaN, NaN, 4], isNaN))
> false

La función todos es una típica función de primer orden que ademas de ser fácil de implementar es realmente útil. Antes de pasar a la siguiente, vamos a refactorizar nuestra función para aprovechar el nuevo ciclo for..of de la implementación ES6 para recorrer los elementos del arreglo.

const _todos = (arr, fn) => {
  let resultado = true
  for(const valor of arr)
    resultado = resultado && fn(valor)
  return resultado
}

Función algunos

Muy similar a la función todos, tenemos esta función llamada algunos, aunque funciona de forma un poco diferente porque esta devuelve true solo si la función regresa true para algunos de los elementos del arreglo. Para poder implementar esta función usaremos || en lugar de &&.

const _algunos = (arr, fn) => {
  let resultado = false
  for (const valor of arr)
    resultado = resultado || fn(valor)
  return resultado
}

Antes de continuar, ya sé que estas implementaciones son muy ineficientes. todos debe recorrer el arreglo hasta el primer elemento que no cumpla con el criterio y algunos recorre el arreglo hasta la primera coincidencia. Para arreglos muy grandes sería muy ineficiente. Pero como el objetivo es entender los conceptos de las funciones de primer orden, le daremos preferencia a la parte didáctica.

Vamos a ver como funciona nuestra función algunos.

> consola(algunos([NaN, NaN, 4], isNaN))
true
> consola(algunos([3, 4, 4], isNaN))
false

Todavía nos falta un ejemplo más complejo que involucra el concepto de abstracción, pero seguiremos en el siguiente artículo.

La función ordenarPor

En ECMAScript 6 existe una función interna sort() en el prototipo de Array que se usa de la siguiente manera. Supongamos que necesitamos ordenar una lista de frutas.

let frutas = ['sandias', 'peras', 'manzanas']

podemos usar la función sort() que contiene el prototipo, simplemente haciendo esto:

> frutas.sort()
[ 'manzanas', 'peras', 'sandias' ]

Es así de simple. sort() es una función de primer orden que toma una función como argumento y que usa como criterio de ordenación. En términos simples, podemos describir la función sort() de la siguiente forma:

arreglo.sort([funciónCriterioOrdenación])

Aquí la función funciónCriterioOrdenación es opcional. Si no se proporciona, los elementos para ser ordenados se convierten en cadenas que se comparan por medio de su código Unicode. No tenemos que preocuparnos por la conversión a Unicode en este momento, así que la dejamos de lado. Lo que importa es que para poder ordenar los elementos usando nuestro propio criterio, necesitamos pasar nuestra función funciónCriterioOrdenación(). En este sentido, podemos ver la flexibilidad que tienen las funciones de primer orden.

Para poder crear nuestra función criterioOrdenacion debemos cumplir con las características especificadas aquí: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Array/sort

función criterioOrdenacion(x, y) {
  si (x es menor que y de acuerdo al criterio) {
    regresar -1
  }
  si (x es mayor que y de acuerdo al criterio) {
    regresar 1
  }
  // si x es igual a y
  regresar 0
}

Para facilitar la comprensión, pensemos en el siguiente ejemplo:

const personas = [
  {nombre: "aaaNombre", paterno: "cccPaterno"},
  {nombre: "cccNombre", paterno: "aaaPaterno"},
  {nombre: "bbbNombre", paterno: "bbbPaterno"},
]

Necesitamos ordenar personas por la clave nombre, por lo que vamos a pasarle a la función sort() nuestra función que sirva como criterio de ordenación:

> personas.sort((x, y) => {
...   return (x.nombre < y.nombre) ? -1: (x.nombre > y.nombre) ? 1 : 0
... })

lo que da como resultado:

[ { nombre: 'aaaNombre', paterno: 'cccPaterno' },
  { nombre: 'bbbNombre', paterno: 'bbbPaterno' },
  { nombre: 'cccNombre', paterno: 'aaaPaterno' } ]

Ahora si queremos usar como criterio de ordenación el apellido paterno, entonces debemos hacer lo siguiente:

personas.sort((x, y) => {
  return (x.paterno < y.paterno) ? -1: (x.paterno > y.paterno) ? 1 : 0
})

que nos regresa el resultado esperado:

[ { nombre: 'cccNombre', paterno: 'aaaPaterno' },
  { nombre: 'bbbNombre', paterno: 'bbbPaterno' },
  { nombre: 'aaaNombre', paterno: 'cccPaterno' } ]

Vamos a regresar un momento a nuestro algoritmo de ordenación:

función criterioOrdenacion(x, y) {
  si (x es menor que y de acuerdo al criterio) {
    regresar -1
  }
  si (x es mayor que y de acuerdo al criterio) {
    regresar 1
  }
  // si x es igual a y
  regresar 0
}

Ahora que ya vimos como funciona, ¿podemos mejorarlo? El lugar de escribir la función criterioOrdenacion cada vez, ¿podríamos abstraer la lógica en una función de primer orden? Como pudimos observar el código para comparar el nombre o el apellido paterno es idéntico, solo cambiaba la clave o propiedad.

Nuestra función de primer orden no va tomar como argumento una función, sino que va a devolver una función. Vamos a llamar a esta función ordenarPor, que permite ordenar un arreglo de objetos usando como criterio de ordenación la propiedad que le pasemos como argumento.

const _ordenarPor = (propiedad) => {
  return (a, b) => {
    let resultado = (a[propiedad] < b[propiedad]) ? -1 : (a[propiedad] > b[propiedad]) ? 1 : 0
    return resultado
  }
}

La función ordenarPor toma como argumento a propiedad y regresa una función que toma dos argumentos:

...
  return (a, b) => { }
...

Esta función lo que hace es usar la lógica que vimos en el algoritmo de criterioOrdenacion:

    let resultado = (a[propiedad] < b[propiedad]) ? -1 : (a[propiedad] > b[propiedad]) ? 1 : 0

Imaginemos que llamamos a la función ordenarPor usando la propiedad nombre. Lo que pasaría es que propiedad será reemplazado por nombre y se convertiría en:

    let resultado = (a[nombre] < b[nombre]) ? -1 : (a[nombre] > b[nombre]) ? 1 : 0

Y eso es lo que hicimos cuando escribimos manualmente la función. Veamos como funciona:

> consola(personas.sort(ordenarPor('nombre')))

> cerebro@0.1.0 start /Volumes/datos/Proyectos/cerebro
> babel-node src/index.js

[ { nombre: 'aaaNombre', paterno: 'cccPaterno' },
  { nombre: 'bbbNombre', paterno: 'bbbPaterno' },
  { nombre: 'cccNombre', paterno: 'aaaPaterno' } ]

Process finished with exit code 0

Y para ordenar por apellido paterno

> consola(personas.sort(ordenarPor('paterno')))
> cerebro@0.1.0 start /Volumes/datos/Proyectos/cerebro
> babel-node src/index.js

[ { nombre: 'cccNombre', paterno: 'aaaPaterno' },
  { nombre: 'bbbNombre', paterno: 'bbbPaterno' },
  { nombre: 'aaaNombre', paterno: 'cccPaterno' } ]

Process finished with exit code 0

En este ejemplo usamos varias funciones de primer orden: consola para mostrar el resultado, sort() una función interna para ordenar y ordenarPor para definir el criterio de ordenación. Y de eso se tratan las funciones de alto nivel, de abstracción.

Antes de terminar, debemos notar que la función ordenarPor es una función que se pasa como parámetro a la función sort(), pero lleva el argumento propiedad que le pasamos. ¿Cómo es posible? Gracias a los cierres o closures que empezaremos a ver en el siguiente artículo.

Javier Sanchez Toledano

Soy programador en Django+Python y WordPress. Auditor líder certificado en la norma ISO 9001:2008. Fotógrafo aficionado.
Redes Sociales:

Tlaxcala, México

Comentarios