Skip to content

Capítulo 02: Funciones de Primera Clase

Revisión Rápida

Cuando decimos que las funciones son 'de primera clase', queremos decir que son como cualquier otro tipo de dato. Es decir, pueden almacenarse en arrays, pasarse como parámetros, asignarse a variables, sin ninguna particularidad especial.

Esto es JavaScript básico, pero vale la pena mencionarlo porque una búsqueda rápida en GitHub revela una evasión colectiva o desconocimiento generalizado de este concepto. Veamos un ejemplo simulado:

js
const hi = name => `Hi ${name}`;
const greeting = name => hi(name);

Aquí, el envoltorio de función alrededor de hi en greeting es redundante. ¿Por qué? Porque las funciones en JavaScript son invocables. Cuando hi tiene () al final, se ejecuta y retorna un valor. Sin ellos, simplemente retorna la función almacenada. Compruébelo usted mismo:

js
hi; // name => `Hi ${name}`
hi("jonas"); // "Hi jonas"

Dado que greeting solo llama a hi con el mismo argumento, podemos escribir directamente:

js
const greeting = hi;
greeting("times"); // "Hi times"

Si hi ya es una función que espera un argumento, ¿para qué envolverla en otra que le pasa el mismo maldito argumento? Es como usar un abrigo pesado en pleno julio para luego prender el aire acondicionado y pedir un helado.

Es extremadamente verboso y una mala práctica envolver funciones innecesariamente solo para retrasar evaluación (veremos problemas de mantenimiento).

Entender esto es crucial. Examinemos ejemplos reales de paquetes npm:

js
// ignorant
const getServerStuff = callback => ajaxCall(json => callback(json));

// enlightened
const getServerStuff = ajaxCall;

El mundo está lleno de código AJAX como este. Su equivalencia se demuestra así:

js
// this line
ajaxCall(json => callback(json));

// is the same as this line
ajaxCall(callback);

// so refactor getServerStuff
const getServerStuff = callback => ajaxCall(callback);

// ...which is equivalent to this
const getServerStuff = ajaxCall; // <-- look mum, no ()'s

Amigos, así es cómo debe hacerse. Insistimos en esta lógica por una razón.

js
const BlogController = {
  index(posts) { return Views.index(posts); },
  show(post) { return Views.show(post); },
  create(attrs) { return Db.create(attrs); },
  update(post, attrs) { return Db.update(post, attrs); },
  destroy(post) { return Db.destroy(post); },
};

Este controlador tiene 99% de código redundante. Podemos reescribirlo como:

js
const BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy,
};

...o eliminarlo por completo, pues solo agrupa Views y Db.

¿Por qué Preferir Funciones de Primera Clase?

Como vimos en los ejemplos, añadir capas de abstracción sin valor solo incrementa código redundante y dificulta el mantenimiento.

Además, si modificamos una función envuelta innecesariamente, también debemos modificar su envoltorio.

js
httpGet('/post/2', json => renderPost(json));

Si httpGet cambiara para manejar un posible err, tendríamos que ajustar todo el 'código pegamento' (glue code).

js
// go back to every httpGet call in the application and explicitly pass err along.
httpGet('/post/2', (json, err) => renderPost(json, err));

Al usar funciones de primera clase, los cambios serían mínimos:

js
// renderPost is called from within httpGet with however many arguments it wants
httpGet('/post/2', renderPost);

Además de eliminar funciones innecesarias, debemos nombrar argumentos cuidadosamente. Los nombres ambiguos causan confusión, especialmente con el tiempo.

Tener múltiples nombres para un mismo concepto genera confusión. Observa estas dos funciones equivalentes con distinta generalidad:

js
// specific to our current blog
const validArticles = articles =>
  articles.filter(article => article !== null && article !== undefined),

// vastly more relevant for future projects
const compact = xs => xs.filter(x => x !== null && x !== undefined);

Nombres específicos como articles nos atan a datos concretos, llevando a reinventar soluciones.

Cuidado con this: si una función lo usa y la llamamos como de primera clase, puede causar fugas de abstracción.

js
const fs = require('fs');

// scary
fs.readFile('freaky_friday.txt', Db.save);

// less so
fs.readFile('freaky_friday.txt', Db.save.bind(Db));

Db, al vincularse a sí mismo, puede acceder a código problemático en su prototipo. En programación funcional evite this, aunque a veces hay que ceder al integrar bibliotecas.

Si prioriza microoptimizaciones con this, quizás prefiera libros más técnicos como 'Introducción a los Algoritmos'.

Con esto, estamos listos para continuar.

Capítulo 03: Felicidad Pura con Funciones Puras