Programación Funcional en JavaScript: Los Objetos

4693609597_43929640fe_b

Los objetos y su herencia son como una matrioska infinita.

Continuamos con la serie dedicada a programación funcional en JavaScript. Nos encontramos en unos primeros capítulos introductorios donde vamos a sentar las bases y aprendiendo poco a poco lo necesario.

Si el otro día hablábamos sobre el porqué usar programación funcional puede ser una buena opción como estilo de programación y forma de pensar, hoy nos vamos a centrar en el porqué elegir JavaScript como lenguaje para programar funcionalmente es una buena opción.

Veremos también una comparación entre programación funcional y programación orientada objetos. Terminaremos el post explicando ciertas peculiaridades a la hora de usar los objetos JavaScript en un modelo como el funcional y explicaremos ciertos trucos para que nos puedan ser útiles.

¿Os interesa? ¿Continuais conmigo? Adelante:

¿Por qué JavaScript?

Dentro de los pros y los grandes contras que puede llegar a tener un lenguaje como JavaScript hay que reconocerle una serie de logros importantes que la comunidad ha valorado siempre en su justa medida.

JavaScript se está convirtiendo en un lenguaje omnipresente. Actualmente puedes desarrollar aplicaciones de toda índole usando JavaScript como lenguaje de programación: tanto aplicaciones de escritorio, como móviles, puedes desarrollar en Web, en su parte front y back, hasta puedes realizar trabajos de big data y machine learning con JavaScript.

Otra característica a tener en cuenta es su naturaleza híbrida. JavaScript es un lenguaje tan voluble o dinámico que podemos seguir varios estilos de programación tan distintos como lo son la programación funcional o la programación orientada a objetos. Este componente híbrido lo hace muy interesante porque podemos obtener ventajas de ambos estilos y crear un modelo de programación muy flexible.

Además, la comunidad que hay detrás del lenguaje es enorme. El lenguaje se encuentra en constante evolución y mejora. Actualmente nos encontramos en la versión 6 de su especificación. Esta versión nos ha traído una gran cantidad de mejoras que favorecen el uso de JavaScript como un lenguaje funcional. A lo largo de los post nos iremos haciendo eco de todas ellas.

Cómo véis es una opción a tener en cuenta. Esto no significa que JavaScript sea la única opción y puede que el lenguaje no sea tan bueno como lo puedan ser Scala, Haskell o F# para programación funcional, sin embargo sus características lo hacen una opción viable y cómoda para el programador.

Programación funcional vs Programación Orientada a Objetos

Como hemos dicho, JavaScript se nos presenta como un módelo híbrido que cuenta con elementos de la programación funcional y con elementos de la programación orientada a objetos.

Por tanto, sería bueno que comparásemos cuáles son las diferencias que presenta cada modelo y cual es el punto intermedio que nos gustaría usar en JavaScript para conseguir desarrollar funcionalmente.

La programación orientada objetos pende del árbol de los lenguajes imperativos. La idea de la orientación a objetos es la de compactar un grupo de datos comunes que tengan un sentido lógico y que consigan abstraer una pequeña parte de nuestro dominio de negocio.

Un objeto cuenta con atributos y con métodos encargados de poder manipular estos valores. Por lo tanto, dentro de un objeto debe existir un gran acoplamiento entre los datos y los métodos que contiene internamente.

La unidad de composición, de este estilo de programación por tanto, es el objeto. La composición se realiza por medio de herencia. Yo puedo en un modelo orientado a objetos hacer que un objeto hijo herede el comportamiento y los atributos de un objeto padre. De esta manera reutilizamos código.

La orientación a objetos favorece la mutación del estado de una aplicación, por tanto para conseguir una integridad de los datos, existen mecanismos de protección. La forma habitual de control de flujo son los bucles y los condicionales.

La forma habitual de programar en orientación a objetos es la siguiente:

class Person {
    constructor(name, lastname) {
        this._name = name;
        this._lastname = lastname;
    }

    getFullName() {
        return `${this._name} ${this._lastname}`;
    }
}
const person = new Person('John', 'Doe');
person.getFullName(); // John Doe

Esto es una clase de JavaScript con ES6. las palabras reservadas class y constructor son azúcar sintáctico. El concepto de clases en JavaScript no existe. En JavaScript se sigue un modelo de objetos prototípicos. Es un concepto mucho más dinámico que el concepto de clase que solo puede ser creado en tiempo de escritura. En JavaScript un objeto puede cambiar su forma y su composición en tiempo de ejecución gracias a este concepto de prototipos.

Sin embargo ya vemos muchas de las cosas que indicábamos en su descripción. Las funciones y atributos se encuentran muy acoplados. En un módelo funcional, la función ‘toString’, si es extraída de su objeto, puede provocar efectos secundarios ya que hace referencia a variables que no se han pasado por parámetros.

Además, el uso de ‘this’ en JavaScript es bastante peculiar ya que this puede apuntar a un dato diferente dependiendo de en qué contexto se esté ejecutando la función. Por lo tanto, trabajar con objetos en JavaScript es harto conflictivo.

El modelo funcional ya fue descrito en el anterior post, pero si podríamos hacer un resumen comparativo de lo que le define:

  • La unidad de composición es la función.
  • Se usa un estilo de programación declarativo.
  • No hay acoplamiento entre los datos y las funciones puras.
  • Trata los estado de un programa como valores inmutables.
  • El control de flujo se realiza por medio de funciones y recursividad.

En programación funcional, el anterior ejemplo sería reescrito de la siguiente manera:

function getFullName(person) {
    return `${person._name} ${person._lastname}`;
};

El objeto ahora no contiene comportamiento, la función se ha extraido, evitando el acoplamiento y los únicos valores con los que se trabaja son los que entran como parámetros de la función.

La función que se obtiene es bastante polimórfica porque acepta cualquier objeto de tipos Person, sus derivados o cualquier objeto que contenga los atributos ‘name’ y ‘lastname’.

Sin embargo, como vemos, compactar los valores dentro de entidades que tengan sentido lógico es una buena forma también de trabajar en programación funcional. Nos dan una mayor consistencia en cuanto a tipos y nos da mayor valor semántico.

Por cierto, – y como apunte personal – la función anterior podría quedar incluso más sencilla creando una expresión con una ‘lambda function’ de ES6:

var getFullName = person => [person.name, person.lastname].join(' ');

Lo que nos queda por ver, es como JavaScript nos permite lidiar con ambas funcionalidades (uso de objetos con funciones puras) sin que el modelo funcional se resista.

Gestionar el estado con objetos JavaScript

Con el estado de un programa, nos solemos referir a la imagen global que tenemos de todos los datos almacenados en los objetos de una aplicación en un instante de tiempo determinado.

Desafortunadamente, JavaScript suele ser muy criticado por el dinamismo que pueden presentar los objetos en tiempo de ejecución. Yo, en JavaScript, puedo crear, eliminar y modificar cualquier atributo de un objeto en tiempo de ejecución, sin que el programa dispare ninguna advertencia o error. Es uno de esas armas de doble filo con la que cuenta el lenguaje.

Además, los objetos en JavaScript no tienen un mecanismo de protección de sus datos. No podemos crear atributos privados. Cualquiera que manipule nuestro objeto, puede modificar el estado completo del mismo.

Por lo tanto, nos vemos inmersos en un pequeño problema. El estilo funcional defiende la premisa de mantener el estado de una aplicación lo más inmutable posible. Si los objetos JavaScript no presentan unas garantías sobre la integridad de los datos, nos puede ser difícil mantener este sistema funcional.

La comunidad, con programadores más inteligentes que yo, ya ha pensado en ello y actualmente ya existen una serie de patrones que nos pueden ayudar a evitar que nuestros objetos presenten problemas de mutabilidad. Para este post os voy a exponer estos 3:

  • Tratar a los objetos como valores
  • Deep-Freezing de miembros mutables
  • Navegar y modificar un object graph con lenses

Vamos allá:

Tratar los objetos como valores

Como llevamos avisando, tenemos un problema a la hora de trabajar con objetos en JavaScript y es que el lenguaje como tal no nos lo pone facil para que estos sean inmutables.

Actualmente, ES6 cuenta con una nueva forma de trabajar con variables constantes gracias a la palabra reservada ‘const’. Pero, como ya os explicaba en este otro post, las propiedades de un objeto siguen sin ser inmutables aunque declaremos el objeto como ‘const’. Tiene su lógica ya que lo que hacemos inmutable es la referencia en memoria del objeto y no los valores del mismo. Por lo tanto, deberemos buscarnos una mejor forma de conseguir esta inmutabilidad de las propiedades.

Una buena estrategia que suele usar los lenguajes orientados a objetos es encapsular las propiedades de un objeto. Si nosotros como desarrolladores podemos indicar si una propiedad puede ser sobreescrita o no, podríamos decir que tenemos objetos inmutables.

Otro problema que surge con JavaScript es que tampoco existe la encapsulación de manera sintáctica en el lenguaje – JavaScript siempre ayudándonos – Todas las propiedades de un objeto en JavaScript son siempre públicas. Sin embargo, existen técnicas para poder fingir la encapsulación. Una buena aproximación es por medio del patrón Objeto Valor que se puede implementar en JavaScript. Estudiemos el siguiente código:

function createPerson(name, lastname) {
    let _name = name;
    let _lastname = lastname;

    return {
        name: function () {
            return _name;
        },
        lastname: function () {
            return _lastname;
        },
        toString: function () {
            return `${_name} ${_lastname}`;
        }
    };
}

Ahora sí. Con esta función hemos conseguido crear un objeto que solo permite acceso de lectura a las propiedades y no de escritura. Tanto JavaScript como en muchos de los lenguajes funcionales, existe una funcionalidad propia de las funciones muy interesante denominada ‘closure’.

Aunque profundizaremos en el concepto en el próximo post, quedémonos, ahora mismo, con que el objeto con funciones que devuelve la función ‘createPerson’ tiene acceso a las variables internas de la función padre. El estado interno de la función padre (‘createPerson’) permanecerá.

Sí, si vienes de entornos imperativos es un concepto bastante raro porque cuando tu declaras una variable en un método, cuando este método o función ha terminado de ejecutarse, todas las variables de su contexto son eliminadas y no se mantiene el estado de las mismas. En funcional, gracias a los ‘closures’, no. Y es algo que hace a las funciones muy potente.

Sin embargo, aunque hemos hecho progresos, el objeto que devuelve ‘createPerson’ todavía puede sufrir modificaciones (se pueden eliminar o modificar miembros y métodos) con lo que no aseguramos una inmutabilidad pura de los objetos. Necesitamos otras técnicas para esto.

Deep-Freezing de miembros mutables

En JavaScript no existe sintaxis especial para convertir un objeto en inmutable, sin embargo existe una funcionalidad muy útil que permite cambiar ciertas metapropiedades de los atributos de un objeto. Una de las metapropiedades indica si una propiedad dentro de un objeto es de escritura. Si ponemos esta metapropiedad a false, conseguiremos un objeto inmutable.

Para conseguir esto usamos el método Object.freeze():

const person = Object.freeze({ 
    name: 'John',
    lastname: 'Doe'
});

person.name = 'Joan'; // Obtenemos un fallo.


Este método funciona correctamente con propiedades heredadas de objetos padre. Sin embargo, JavaScript nos la vuelve a jugar ya que ‘Object.freeze’, no funciona con las propiedades de un objeto interno. Esto ocurre en el siguiente ejemplo:

const person = Object.freeze({ 
    name: 'John',
    lastname: 'Doe',
    address: {
        street: 'Hans Boulevar',
        number: '2'
    }
});

person.address.street = 'Alabama'; // En este caso si cambia la variable.

Bueno, esto tiene solución. El lenguaje no nos ofrece nada sintácticamente para conseguir que todos los objetos que existan dentro de los objetos se conviertan en inmutables, pero la siguiente función puede hacerlo, recorriendo el objeto de manera recursiva:

var isObject = (val) => val && typeof val === 'object';

function deepFreeze(obj) {
    if (isObject(obj) && !Object.isFrozen(obj)) {
        Object.keys(obj).forEach(name => deepFreeze(obj[name]));
        Object.freeze(obj);
    }
    return obj;
}

La función anterior es bastante fácil de seguir: lo que hace es comprobar si el parámetro pasado es un objeto y si aún no ha sido ‘congelado’ con el método ‘Object.isFrozen’. Si cumple la condición, se crea un array con cada uno de los atributos y se recorre para recursivamente comprobar cada una.

El caso base se da cuando no existen más propiedades de tipo objeto. El resultado final es un objeto totalmente inmutable.

Navegar y modificar un ‘object graph’ con ‘lenses’

En orientación a objetos estamos muy acostumbrados a que el estado interno de un objeto cambie constantemente. En programación funcional esto no es una opción y sería recomendable no mutar estados. Como esto no es posible, la mejor solución sería crear una copia del objeto nueva que contenga el nuevo valor de la propiedad que deseamos cambiar.

Por ejemplo imaginemos que queremos cambiar el ‘lastname’ del objeto. Una buena opción sería esta:

set lastname(newLastName) {
 return new Person(this._name, newLastName);
}

De esta manera evitamos el cambio de estado, creando un objeto nuevo que tiene el comportamiento deseado. Tenemos muy controlado el cambio y se parece al ‘seteo’ de orientación a objetos.

El problema viene cuando nuestro objeto tiene una cantidad de propiedad y objetos internos mayor. La cantidad de código de infraestructura que tenemos que meter es bastante para lo que necesitamos hacer. Perdemos el foco de lo que debería ser importante, desarrollar lógica de negocio de nuestro dominio, no deberíamos hacer demasiado código para nuestros inventos.

Bueno, vemos que el copy-on-write no es muy práctico, pero nos ayuda a explicar lo que hace una librería como RambdaJS. Esta librería nos ayuda a incluir este mecanismo de write-on-copy en nuestras aplicaciones sin tener que ensuciar nuestros objetos. De esta manera podríamos tener un set y get más típicos de la programación orientada a objetos pero basándose en los principios funcionales.

Por ejemplo para hacer un ‘set’ y ‘get’ de un objeto ‘person’, lo haríamos así:

const person = {
   name: 'John',
   lastname: 'Doe'
};
const lastnameLens= R.lenseProp('lastname');
R.view(lastnameLens, person); // get
R.set(lasntnameLens, 'Dorne', person); // set

Conclusión

JavaScript nos proporciona un módelo híbrido en el que podemos unir lo mejor de la programación funcional y lo mejor de la programación orientada a objetos. La línea imaginaria que nos marca donde usamos un estilo y donde usamos el otro, será una decisión que nosotros, como programadores, tendremos que tomar.

Los objetos, en modelos funcionales, pueden ayudar a abstraer y almacenar, de una forma compacta, el estado de una aplicación. Tenemos que tener cuidado con los problemas que nos pueda ocasionar JavaScript y su diseño de los objetos. Si queremos seguir pensando de una forma funcional, intentaremos seguir, en la medida de lo posible, las técnicas que hemos explicado. Si es así, no debería existir mayor problema.

En el próximo post, hablaremos sobre las peculiaridades que podemos encontrarnos sobre las funciones en JavaScript, y como estas se convierten en el actor principal a tener en cuenta para llevar a cabo nuestro propósito funcional.

Nos leemos 🙂

Aquí os dejo el mapa conceptual del post:

javascript

Imagen de portada | flickr

17 comentarios

  1. Pingback: Programación funcional en JavaScript: Las funciones | el.abismo = de[null]
  2. Pingback: Programación Funcional en JavaScript: Los métodos funcionales | el.abismo = de[null]
  3. Pingback: Programación Funcional en JavaScript: La recursividad | el.abismo = de[null]
  4. Pingback: Programación Funcional en JavaScript: La aridad y las tuplas | el.abismo = de[null]
  5. Pingback: Programación Funcional en JavaScript: La currificación | el.abismo = de[null]
  6. Pingback: Programación Funcional en JavaScript: La composición | el.abismo = de[null]
  7. Pingback: Programación Funcional en JavaScript: Los combinadores | el.abismo = de[null]
  8. Pingback: Programación Funcional en JavaScript: Los funtores | el.abismo = de[null]
  9. Pingback: Programación Funcional en JavaScript: La mónada Maybe | el.abismo = de[null]
  10. Pingback: Programación Funcional en JavaScript: La mónada Either | el.abismo = de[null]
  11. Pingback: Programación Funcional en JavaScript: La mónada IO | el.abismo = de[null]
  12. Pingback: Programación Funcional en JavaScript: La evaluación perezosa | el.abismo = de[null]
  13. Pingback: Programación Funcional en JavaScript: La memoización | el.abismo = de[null]
  14. Aaron Planell López · febrero 21, 2017

    ¡Genial! También muy bien explicado.

    Quizás sólo un par de correcciones que te pueden ser útiles:
    1) «protitipicos» -> Diría que es prototípicos
    2) En: «Se usa un estilo de programación declarativo.» -> Negrita de la última letra
    3) En vez de: return `${person.name} ${person._lastname}`;
    Es: return `${person._name} ${person._lastname}`;
    4) En vez de: Ahora si.
    Es: Ahora sí.
    5) En vez de: clousure
    Es: closure
    6) En vez de: Si, si vienes de entornos imperativos
    Es: Sí, si vienes de entornos imperativos
    7) Sobre librerías de inmutabilidad, quizás nombraría Immutable.js de Facebook.

    Lo dicho, si quieres elimina el comentario al finalizar.

    ¡Gracias por el post!

    Le gusta a 1 persona

  15. DaniloG · marzo 3, 2017

    Excelente tema, te he entendido bastante bien. SIn duda la PF es excelente y veo que puede ayudarte a evitarte errores a lo tonto por culpa del cambio de estados que fácilmente cometemos. Sólo quería señalar un pequeño error al redactar. Escribiste «Yo puedo en un modelo orientado a objetos hacer que un objeto padre herede el comportamiento y los atributos de un objeto padre. De esta manera reutilizamos código.»

    Supongo que a lo que te refieres es a un objeto hijo que hereda del padre.

    Le gusta a 1 persona

Deja un comentario