JS: Funciones

Teoría: Funciones de orden superior

En JavaScript, hay un método incorporado llamado sort que ordena un array. Por defecto, este método ordena el array de una manera bastante complicada: convierte cada elemento del array en una cadena de texto y compara esas cadenas basándose en el orden de los puntos de código Unicode. Este tipo de ordenación es adecuada para situaciones simples, como valores numéricos, pero deja de funcionar cuando se necesita una comparación más compleja, por ejemplo, cuando cada elemento del array es un objeto.

Imaginemos la siguiente situación: se recibe una lista de usuarios como entrada en un programa y se necesita ordenarla por edad y mostrarla en pantalla.

const users = [
  { name: 'Sergio', age: 19 },
  { name: 'José', age: 1 },
  { name: 'Ana', age: 4 },
  { name: 'Miguel', age: 16 },
];

La ordenación por defecto no puede ordenar correctamente este tipo de array. Esto se aplica a cualquier tipo de ordenación que podamos necesitar. Podemos querer ordenar por cualquier parámetro (o incluso por un conjunto de parámetros) y en cualquier orden. Las ordenaciones son comunes y muchas de ellas son bastante complejas.

En lenguajes donde las funciones no son datos (objetos de primera clase), tendríamos que implementar una función sort() separada para cada tipo de ordenación. Pero en JavaScript hay una forma mejor. Veamos la definición del método sort() incorporado:

elements.sort([compareFunction])

Toma un parámetro opcional compareFunction() que es una función que especifica cómo ordenar el array. La idea general es que no necesitamos implementar el algoritmo de ordenación cada vez para cada situación, ya que el algoritmo en sí no cambia. Lo único que cambia son los elementos que se comparan durante el proceso de ordenación. Y el método sort() delega la interacción con esos elementos a nuestro código:

const users = [
  { name: 'Sergio', age: 19 },
  { name: 'José', age: 1 },
  { name: 'Ana', age: 4 },
  { name: 'Miguel', age: 16 },
];

// La función toma los elementos del array que se están comparando como entrada
const compare = (a, b) => {
  if (a.age === b.age) {
    return 0;
  }

  return a.age > b.age ? 1 : -1;
};

users.sort(compare);

console.log(users);
// => [ { name: 'José', age: 1 },
//      { name: 'Ana', age: 4 },
//      { name: 'Miguel', age: 16 },
//      { name: 'Sergio', age: 19 } ]

El método sort() se encarga de todo el trabajo de mover los elementos en el array. Pero qué elemento es mayor o menor depende del programador. Esto se logra mediante la función personalizada que se pasa al llamar a sort(). Esta función toma dos parámetros: sort() les pasa los dos elementos que se están comparando en ese momento. En nuestro caso, los elementos son usuarios. Su tarea es calcular cuál es mayor o menor y hacer lo siguiente: si los elementos son iguales, debe devolver 0; si el primer elemento es mayor que el segundo, se considera que están ordenados incorrectamente y se devuelve 1; de lo contrario, se devuelve -1 y sort() los ordena.

A partir del código, se puede ver que dentro de la función se realiza la comparación basada en la propiedad age de los usuarios pasados. No es difícil darse cuenta de que esta función se llama dentro de sort() muchas veces (es decir, en cada comparación). Tan pronto como comience a devolver -1 para cada par de elementos, la ordenación se completa.

El método sort() se considera una función de orden superior (higher order function). Las funciones de orden superior son aquellas que aceptan o devuelven otras funciones, o incluso hacen ambas cosas a la vez. Estas funciones suelen implementar un algoritmo general (como la ordenación) y delegan la parte clave de la lógica al programador a través de una función. La principal ventaja de utilizar estas funciones es reducir la duplicación de código.

La función que se pasa dentro del método sort() tiene su propio nombre. Estas funciones se llaman callbacks (retroalimentación, llamada de vuelta). Un callback es cualquier función que no es llamada directamente por el programador, sino que es llamada por otra función a la que pasamos nuestro callback.

En el ejemplo anterior, no es necesario crear una constante para la función. De hecho, rara vez se asignan a constantes. El uso típico es pasar la función directamente a la función:

users.sort((a, b) => {
  if (a.age === b.age) {
    return 0;
  }
  return a.age > b.age ? 1 : -1;
});

// Lo mismo, pero usando la función Math.sign
users.sort((a, b) => Math.sign(a.age - b.age));

Tómate un momento para comprender dónde termina una función y comienza la otra. A partir de la próxima lección, comenzaremos a utilizar este tipo de código.

Ahora veamos cómo se realiza la llamada internamente. Desde el punto de vista de la sintaxis, no hay nada nuevo.

const say = (fn) => {
  const message = fn();
  console.log(message);
};
// o así:
// const say = (fn) => console.log(fn());

const myCallbackFn = () => '¡Hola!';
say(myCallbackFn); // => ¡Hola!
// o así:
// say(() => '¡Hola!');

La función say() llama a la función que se encuentra dentro del parámetro fn. En nuestro ejemplo, la función devuelve una cadena de texto que se muestra inmediatamente en la pantalla.

Las funciones de orden superior son tan convenientes en la mayoría de los lenguajes que prácticamente pueden reemplazar el uso de bucles. Por ejemplo, el código canónico en JS se ve así:

// Solo una demostración
// No es necesario analizarlo
users
  .filter((user) => user.age >= 16)
  .map((user) => `${user.name} tiene ${user.age} años`)
  .join('\n');
// => Sergio tiene 19 años
//    Miguel tiene 16 años

En este código, hay 2 funciones de orden superior (filter() y map()), 2 funciones como argumentos y dos pasadas (realizadas por las funciones de orden superior) a través de la lista de usuarios. El código es expresivo

Completado

0 / 16