ÂŋAlguna vez han escuchado el tÃĐrmino "funciÃģn pura"? ÂŋY "efecto secundario"? Si la respuesta es sà entonces probablemente les han dicho que los efectos secundarios son malos y deben evitarlos a toda costa. Este es el problema, si estÃĄn usando javascript es muy probable que quieran causar esos efectos (especialmente si les pagan por usar javascript) Entonces la soluciÃģn no es evitar estos efectos sino controlarlos. Voy a mostrarles algunas maneras en las que pueden combinar los efectos secundarios y las funciones puras.
Antes de empezar vamos a repasar algunos conceptos, para que todos estÃĐn al tanto.
Conceptos
FunciÃģn pura
Para no extenderme mucho dirÃĐ que una funciÃģn pura es aquella cuyo resultado es determinado por sus parÃĄmetros y no tiene ningÚn efecto observable fuera de su ÃĄmbito. El mejor beneficio que proveen es la predictibilidad, dado un conjunto de valores de entrada siempre devolverÃĄn el mismo resultado. VeÃĄmos algunos ejemplos.
Esta es una funciÃģn pura.
function increment(number) {
return number + 1;
}
Esta no.
Math.random();
Y estas son debatibles.
const A_CONSTANT = 1;
function increment(number) {
return number + A_CONSTANT;
}
module.exports ={
increment
};
function a_constant() {
return 1;
}
function increment(number) {
return number + a_constant();
}
Efecto secundario
Llamaremos efecto secundario a cualquier cosa que afecte la "pureza" de una funciÃģn. La lista incluye pero no estÃĄ limitada a:
- Cambiar (mutar) una variable externa en cualquier forma posible.
- Mostrar cosas en la pantalla.
- Modificar un archivo.
- Hacer una peticiÃģn http.
- Crear un proceso.
- Guardar datos en una base de datos.
- Ejecutar funciones con efectos secundarios.
- Cambiar el DOM.
- Aleatoriedad.
Entonces, cualquier cosa que afecte el "estado del mundo exterior" es un efecto secundario.
ÂŋCÃģmo combinamos esas cosas?
Apuesto a que todavÃa estÃĄn pensando en esa lista de efectos, incluye bÃĄsicamente todo lo que hace que javascript sea Útil y aÚn asà hay personas que dicen debes evitarlos cÃģmo sea. No tengan miedo, yo les tengo algunas sugerencias.
ComposiciÃģn de funciones
Otra forma de describir lo que voy decir serÃa esta: separaciÃģn de responsabilidades. Este es la manera mÃĄs simple. Si tienen la oportunidad de separar un cÃĄlculo/transformaciÃģn de un efecto entonces trasladen esa transformaciÃģn a una funciÃģn y usen el resultado en el bloque que contiene el efecto.
En ocasiones puede ser tan simple como este caso.
function some_process() {
const data = get_data_somehow();
const clean_data = computation(data);
const result = save(clean_data);
return result;
}
Ahora bien, some_process sigue siendo una funciÃģn impura pero eso estÃĄ bien, esto es javascript, no necesitamos que todo sea puro, lo que queremos es mantener la cordura. Al separar los efectos de un cÃĄlculo puro hemos creados tres funciones independientes que resuelven un problema a la vez. Pueden incluso ir mÃĄs allÃĄ y utilizar una funciÃģn como pipe para eliminar esos valores intermedios y crear una composiciÃģn mÃĄs directa.
const some_process = pipe(get_data_somehow, computation, save);
Pero ahora hemos creado otro problema, ÂŋQuÃĐ pasa si queremos insertar un efecto en medio de esa cadena? ÂŋQuÃĐ hacemos? Bueno, si una funciÃģn nos metiÃģ en este problema yo digo que usemos otra para salir. Esto servirÃĄ.
function tap(fn) {
return function (arg) {
fn(arg);
return arg;
}
}
Esta funciÃģn nos permitirÃĄ colocar un efecto en nuestra cadena sin afectar la composiciÃģn.
const some_process = pipe(
get_data_somehow,
tap(console.log),
computation,
tap(a_side_effect),
save
);
Algunos dirÃĄn que este tipo de cosas hacen que la lÃģgica de la funciÃģn estÃĐ esparcida por todos lados y ahora tienen que buscar mÃĄs de lo necesario para saber quÃĐ hace la funciÃģn. A mà no me molesta mucho, es asunto de preferencias. Suficiente de eso, hablemos de los argumentos de la funciÃģn tap, mirÃĐnlo tap(fn) acepta una funciÃģn cÃģmo parÃĄmetro, vamos a ver cÃģmo podemos usar eso para otras cosas.
Haz que otro se encargue del problema
Como todos sabemos la vida no siempre es tan simple, habrÃĄ ocasiones en las que simplemente no podemos hacer esa bonita cadena de funciones. A veces necesitamos colocar un efecto en medio de un proceso y cuando eso pasa siempre podemos hacer trampa. Javascript nos permite usar las funciones como si fuera un valor cÃģmun (como un nÚmero) y esto nos da la oportunidad de hacer algo gracioso como usar una funciÃģn como parÃĄmetro de otra funciÃģn (lo que llaman callback). De esta forma una funciÃģn "pura" puede mantener su predictibilidad y al mismo tiempo proveer la flexibilidad de ejecutar un efecto cuando sea conveniente.
Digamos por ejemplo que tenemos una funciÃģn que ya es pura que transforma los valores de una colecciÃģn pero por alguna razÃģn ahora necesitamos escribir en un log el valor original y el nuevo pero justo despuÃĐs de la transformaciÃģn. Lo que podemos hacer es aÃąadir una funciÃģn como parÃĄmetro y llamarla en el momento justo.
function transform(onchange, data) {
let result = Array.isArray(data) ? [] : {};
for(let key in data) {
result[key] = data[key] + 1;
onchange(data[key], result[key]);
}
return result;
}
Esto tÃĐcnicamente cumple los requisitos de una funciÃģn pura, el resultado (y comportamiento) de la funciÃģn estÃĄ determinado por sus parÃĄmetros, sÃģlo que da la casualidad que uno de esos parÃĄmetros es una funciÃģn que puede tener un efecto secundario. De nuevo, la meta no es pelear contra la naturaleza de javascript hacer que todo sea 100% puro, lo que queremos es controlar estos efectos, en este caso quien controla si se debe tener un efecto es quien llama a nuestra funciÃģn y provee los parÃĄmetros. Un beneficio extra que tenemos de esto es que podemos reusar la funciÃģn en pruebas unitarias sin tener que instalar una librerÃa extra, lo Único que tenemos que hacer suministrar parÃĄmetros y evaluar el resultado.
Tal vez se estÃĐn preguntando por quÃĐ pongo el callback como primer parÃĄmetro, es cuestiÃģn de preferencia. Si ponen el valor que cambia con mÃĄs frecuencia en la Última posiciÃģn se les hace mÃĄs fÃĄcil aplicar parcialmente los argumentos, con esto me refiero a vincular parÃĄmetros a una funciÃģn sin ejecutarla. Pueden por ejemplo usar transform.bind para crear una funciÃģn especializada que ya tenga el valor onchange y que sÃģlo espere el argumento data.
Efecto tardÃo
La idea aquà es retrasar lo inevitable. En lugar de ejecutar un efecto en seguida lo que queremos hacer es darle la oportunidad a quien usa nuestra funciÃģn de decidir cuÃĄndo se debe ejecutar el efecto. Podemos hacer de varias maneras.
Devolviendo funciones
Como mencionÃĐ antes, en javascript podemos tratar a las funciones como un valor y una cosa que hacemos con frecuencia es devolver valores de funciones. Estoy hablando de funciones que devuelven funciones, ya vimos lo Útil que puede ser y no es tan inusual si lo piensan bien, ÂŋCuÃĄntas veces han visto algo como esto?
function Stuff(thing) {
// preparar datos
return {
some_method() {
// cÃģdigo...
},
other() {
// cÃģdigo...
}
}
}
Esto es una especie de constructor. Antes, en la era del ES5 esta era una de las maneras en las que se podÃa imitar el comportamiento de una clase. Es una funciÃģn normal que devuelve un objeto, y como todos sabemos los objetos pueden tener mÃĐtodos. Lo que queremos hacer es muy parecido, queremos convertir un bloque que contiene un efecto y devolverlo.
function some_process(config) {
/*
* Hacemos algo con `config`
*/
return function _effect() {
/*
* aquà podemos tener cualquier cosa
*/
}
}
Asà es como le damos la oportunidad a quien llama nuestra funciÃģn de usar el efecto cuando quieran, y pueden incluso pasarlo a otras funciones o usarla en una cadena (como la que hicimos antes). Este patrÃģn no es muy comÚn, tal vez es porque podemos usar otros mÃĐtodos para lograr la misma meta.
Usando estructuras
Otra forma de retrasar un efecto es envolverlo en una estructura. Lo que queremos hacer es tratar un efecto como un valor cualquiera, tener la habilidad de manipularlo e incluso combinarlo con otros efectos de una manera "segura," es decir sin ejecutarlos. Probablemente ya han visto este patrÃģn antes, un ejemplo que puedo dar es con lo que llaman "Observables." Vean este ejemplo que utiliza rxjs.
// extraÃdo de:
// https://www.learnrxjs.io/operators/creation/create.html
/*
Incrementa el valor cada segundo, emite valores de los nÚmeros pares
*/
const evenNumbers = Observable.create(function(observer) {
let value = 0;
const interval = setInterval(() => {
if (value % 2 === 0) {
observer.next(value);
}
value++;
}, 1000);
return () => clearInterval(interval);
});
El resultado de Observable.create no sÃģlo retrasa la ejecuciÃģn de setInterval sino que tambiÃĐn nos da la oportunidad de usar evenNumber.pipe para crear una cadena de observables que tambiÃĐn pueden contener otros efectos. Claro que los Observables y rxjs no son la Única manera, nosotros podemos crear nuestro propia estructura para los efectos. Si queremos crear nuestros propios efectos lo Único que necesitamos es una funciÃģn para ejecutarlos y otra para combinarlos.
function Effect(effect) {
return {
run(...args) {
return effect(...args);
},
map(fn) {
return Effect(arg => fn(effect(arg)));
}
};
}
Puede que no sea mucho pero esto es suficiente para tener algo Útil. Con esto ya pueden empezar a combinar efectos sin causar cambios en su ambiente. Por ejemplo.
const persist = (data) => {
console.log(`guardando ${data} en la base de datos...`);
return data.length ? true : false;
};
const show_message = result => result
? console.log('todo bien')
: console.log('no estamos bien');
const save = Effect(persist).map(show_message);
save.run('algo');
// guardando algo en la base de datos...
// todo bien
save.run('');
// guardando en la base de datos....
// no estamos bien
Si alguna vez han usado Array.map para transformar datos de un arreglo se sentirÃĄn como en casa usando Effect, todo lo que tienen que hacer es suministrar los efectos y al final de la cadena tendrÃĄn una funciÃģn que sabrÃĄ quÃĐ hacer cuando estÃĐn listos para ejecutarla.
Esta es sÃģlo una muestra de lo que pueden hacer con Effect, si quieren aprender un poco mÃĄs busquen por ahà el tÃĐrmino functor y IO monad, ahà tienen diversiÃģn para un buen rato.
ÂŋAhora quÃĐ?
Ahora espero que puedan echarle un vistazo al enlace que estÃĄ al final, es un articulo en inglÃĐs que explica con mejor detalle todo esto que yo describà aquÃ.
Espero que ahora tengan el conocimiento y la confianza para empezar a escribir funciones puras en su cÃģdigo y poder combinarlas con los efectos prÃĄcticos que pueden hacer con javascript.
Fuente
Gracias por su tiempo. Si este artÃculo les pareciÃģ Útil y quieren apoyar mis esfuerzos para crear mÃĄs contenido pueden dejar una propina en ko-fi.com/vonheikemen.

Top comments (0)