Inmutabilidad vs mutabilidad

Inmutabilidad vs mutabilidad

Cuando hablamos de inmutabilidad vs mutabilidad en el código, irremediablemente hablamos de la pelea entre velocidad vs seguridad. Pero primero definamos que es mutabilidad.

Mutabilidad

La mutabilidad sucede cuando cambiamos una variable de un valor a otro. Esto puede suceder al actualizar un numero, cambiar el texto de un mensaje, o incluso cambiar el tipo de dato de una variable. Si hablamos especificamente del estado, como centro único y confiable de los datos (si, ya mas adelante hablaré de Redux), este se ve afectado al usar la mutabilidad. Si ahondamos un poco más en los problemas que involucra la mutabilidad en general, estos pueden ser algunos:

  • Estado indeterminado
  • Interbloqueos en entornos multihilos
  • "Side effects", que es cuando se termina cambiando el tipo de dato sin que lo queramos.

Ahora, si hablamos de las ventájas, podríamos nombrar algunas como:

  • Mayor velocidad y menor consumo de recursos al no tener que generar nuevos objetos al querer cambiar sus valores
  • Simplicidad

Inmutabilidad

La inmutabilidad es lo opuesto a lo mutable, en este caso, es la imposibilidad de cambiar el valor/estado de una variable. ¿Pero, que buscamos con la inmutabilidad? palabras mas, palabras menos, lo que se busca es la persistencia de los datos. Pensemoslo como una "máquina del tiempo", donde cada modificación que realizamos a los datos puede ser consultada. Dentro de la persistencia encontramos 4 clases:

  • Parcial: toda versión modificada es accesible, pero solo se puede cambiar la última.
  • Total: Todas las versiones son accesibles y modificables. (por defecto en lenguajes imperativos)
  • Confluente: Persistencia parcial + las dos ultimas versiones se pueden combinar .
  • Funcional: No se puede modificar ninguna versión ya creada, solo se pueden crear nuevos versiones de los datos apartir de las ya creadas.

Si hablamos especificamente de Javascript, podemos usar la persistencia funcional, usando librerias como immutable js o con vanilla JS usando las propiedades de los objectos como Object.freeze.

Un video donde podemos profundizar un poco mas sobre estos conceptos es el de David Nolen.

Inmutabilidad en Javascript

Redux

Cuando hablamos de inmutabilidad en javascript, nos tenemos que referir sin duda a Redux , Redux es un manejador del estado en una aplicación. Cuando un sistema comienza a tener mayor complejidad, Redux toma la filosofía de que ningún objeto está habilitado para mutar el estado de una aplicación. lo que quiere decir, que la modificación del estado debe hacerse desde un sitio único (el estado)

Immutable JS

Esta libreria toma el estilo funcional de estructuras de datos (linked lists, hash, etc) para crear un sistema completamente funcional, esto provoca una mejora en terminos de limpieza del código y una arquitectura mas clara, pero también provoca un deceso en la velocidad y el aumento del uso de recursos de memoria.

Escribir código mutable seguro

Para escribir código mutable seguro tenemos que dar la impresión de que estamos escribiendo código inmutable, es decir, la interfaz debe parecer inmutable, así internamente sea código mutable. Mejor verlo con un ejemplo:

Array.prototype._map = function(fun) {
   if( typeof fun !== 'function' ) {
     return null;
   }
   const arr = new Array(this.length);
   for(let i = 0; i < this.length; i++) {
     arr[i] = fun(this[i]);
   }
   return arr;
}

Resources Allocation Is Initialization (RAII)

RAII viene de C++ en donde no tenemos un memory manager, encapsulamos la lógica donde potencialmente podemos compartir los recursos que necesitamos liberar despues de ser usados. La ventaja de esto es que no tenemos fugas de memoria, y los objetos son usados de una manera segura. Otro nombre para esto es SBRM (scope bound resource manager), y es utilizado por Rust. Vamos a ver un ejemplo

const ReaderWriter = function() {
    let data = {};
    let readers = 0;
    let readyForSet = new CustomEvent('readydata');
    this.getData = function() {
        readers += 1;
        return data;
    }
    this.releaseData = function() {
        if (readers) {
            readers -= 1;
            if (!readers) {
                document.dispatchEvent(readyForSet);
            }
        }
        return readers;
    }
    this.setData = function(d) {
        return new Promise((resolve, reject) => {
            if (!readers) {
                data = d;
                resolve(true);
            } else {
                document.addEventListener('readydata', function(e) {
                    data = d;
                    resolve(true);
                }, {
                    once: true
                });
            }
        });
    }
}

Creamos un constructor, mantenemos los datos y el numero de readers, y definimos un custom event como si fuera una variable privada. Luego creamos tres metodos, getData obtendrá los datos e incrementara el contador para definir que hay alguien usandolo. El método release decrementará el contador, si llega a 0 despachamos un evento para avisarle a setData que podemos mutar el estado. Finalmente tenemos la función setData donde tenemos una promesa que retorna un value, la cual se resuelve se estén esperando o no datos, en caso contrario, seteamos el event listener para nuestro custom event. Una vez se dispara el evento, los datos son seteados y se resuelve la promesa.

Estos conceptos son solo algunas bases de este tema, recomiendo mucho profundizar en cada uno para obtener un entendimiento mas detallado.