Paradigma de programación en JavaScript
¿Qué es un paradigma de programación?
El modelo o mapa de como vemos el mundo real, eso es un paradigma, es una manera de ver y hacer las cosas. Siguiendo esta lógica, un paradigma de programación no es más que una forma de ver y crear código de programación.
Los paradigmas son poderosos porque crean los cristales o las lentes a través de los cuales vemos el mundo.
Stephen R. Covey
Existe tres principales paradigmas de programación utilizados en la actualidad y que en JavaScript siempre han existido desde su primera versión.
- Paradigma de programación Estructurada
- Orientada a objetos
- Funcional
Ahora bien, si nos apegamos a la frase de Stephen R. Covey, sobre que los paradigmas crean las lentes a través de los cuales vemos el mundo. Imaginemos que los tres paradigmas anteriores, cada uno, son un par de lentes que nos ponemos a la hora de programar y que podemos cambiar esos lentes según nuestras necesidades visuales (de programación o solución de problemas).
Paradigma de programación estructurada
Origen
En el año 1954 apareció el lenguaje de programación FORTRAN, luego en 1958 ALGOL y en 1959 COBOL. Tiempo después Edsger Wybe Dijkstra en 1968 descubre el paradigma de programación estructurada, decimos que “descubre” porque en realidad no lo inventaron. Aunque este paradigma de programación se formalizó tiempo después de la aparición de estos lenguajes de programación, era posible hacer programación estructurada en ellos.
Dijkstra reconoció que la programación era difícil, y los programadores no lo hacemos muy bien, de lo cual estoy totalmente de acuerdo. Un programa de cualquier complejidad tiene muchos detalles para el cerebro humano. De hecho la neurociencia nos indica que la mente enfocada solo puede trabajar con cuatro datos a la vez, cuanto mucho. Por esta razón, si se descuida un pequeño detalle creamos programas que parecen funcionar bien, pero que fallan en formas que uno nunca se imagina.
Pruebas
La solución de Dijkstra fue utilizar pruebas, solo que estas pruebas usaban mucho las matemáticas, lo cual era bastante difícil de implementar. Durante su investigación encontró que ciertos usos de GOTO hacían difícil descomponer un programa grande en piezas más pequeñas, no se podía aplicar “divide y vencerás”, necesario para crear pruebas razonables.
Secuencia, selección e iteración
Böhm y Jacopini probaron dos años antes que todos los programas pueden ser construidos por solo tres estructuras: secuencia, selección e iteración. Dijkstra ya conocía estas tres estructuras y descubrió que estas mismas son las necesarias para hacer que cualquier parte del programa sea probado. Es aquí donde se formaliza el paradigma de programación estructurada.
Edsger nos indica que es una mala práctica usar GOTO
y sugiere usar mejor las siguientes estructuras de control:
- If, then, else (selección)
- do, while, until (iteración o repetición)
Descomposición de pequeñas unidades fáciles de probar
Hoy en día la mayoría de los lenguajes de programación utilizan la programación estructurada, JavaScript utiliza este tipo de programación. El ejemplo más sencillo son las condiciones if.
const EDAD_MINIMA = 18;
if (edad >= EDAD_MINIMA) {
// hacer algo porque es mayor que 18
} else {
// Hacer otra cosa en caso contraio
}
Y si lo aislamos en una función, tenemos una descomposición de funcionalidades o unidades que pueden ser probadas con mayor facilidad. En la siguiente sección veremos la importancia de tener una unidad completamente probable
const EDAD_MINIMA = 18;
function esMayorEdad(edad) {
if (edad >= EDAD_MINIMA) {
return true;
} else {
return false;
}
}
Con esta condición tenemos un control directo de lo que puede suceder si la edad es mayor o igual a 18
, y también en el caso de que la edad sea menor.
Tampoco seamos tan estrictos, seguro que la sentencia GOTO tiene sus méritos y tal vez era muy eficiente en algunos casos, pero pienso que para evitar malos usos que probablemente causaban desastres, Dijkstra recomendó dejar de usar GOTO.
Pruebas, divide y vencerás
La programación estructurada nos permite aplicar la filosofía de “divide y vencerás”, porque nos permite crear pruebas de pequeñas unidades, hasta tener todo el programa cubierto. La solución matemática de Wybe Dijkstra nunca se construyo, precisamente por su dificulta de implementación. Y lamentablemente en nuestros días aún existen programadores que no creen que las pruebas formales sirven para crear software de alta calidad.
Y digo crear, porque verificar el correcto funcionamiento después de crear, no es más que una simple medición y se pierde la oportunidad de reducir tiempo y dinero.
Método científico, experimentos e impirismo
La buena noticia es que el método matemático no es la única forma de verificar nuestro código, también tenemos el método científico. El cual no puede probar que las cosas sean absolutamente correctas desde el punto de vista matemático. Lo que si se puede hacer es crear experimentos y obtener resultados suficientes para verificar que nuestras teorías funcionan. Dado que nuestras teorías funcionan sobre estos experimentos, entonces concluimos que son lo suficientemente “correctas” para nuestros propósitos de validación.
Si la teoría es fácilmente validada como falsa, entonces es momento de modificarla o cambiarla. Así es el método científico y así es como actualmente se hacen las pruebas:
Primero establecemos nuestra teoría (escribimos la prueba), después construimos los experimentos (escribimos código de producción) y finalmente verificamos (corremos la prueba) y repetimos este ciclo hasta tener evidencia suficiente.
¿Esto no te suena a una práctica ágil actual llamada TDD?
Las pruebas muestran la presencia, no la ausencia, de defectos
Edsger Wybe Dijkstra dijo “Las pruebas muestran la presencia, no la ausencia, de defectos”. Ósea, un programa puede ser probado incorrecto mediante pruebas, pero no puede ser probado correcto. Todo lo que podemos hacer con nuestras pruebas es validar que nuestro programa funciona lo suficiente bien para nuestros objetivos. Pero nunca podemos estar cien por ciento seguros de la ausencia de defectos.
Si nunca podemos estar cien por ciento seguros que nuestro código es correcto, aun con sus pruebas, ¿Qué nos hace pensar que sin pruebas entregamos un programa con la suficiente validez para afirmar que no se comete conscientemente alguna negligencia?
El control directo
La programación estructurada nos enseña sobre el control directo, es decir, controlar el flujo secuencial de las operaciones a través de las estructuras de control, sin descuidar la recomendación de Wybe Dijkstra sobre GOTO. Esta recomendación también es aplicable a sentencias que cambian drásticamente el flujo normal de las operaciones, por ejemplo un break
dentro de un ciclo for
anidado, ese break
rompe el control directo porque complica abruptamente el entendimiento del algoritmo. Tampoco seamos puristas, seguro habrá casos que necesites de estas sentencias, pero de primera instancia, trata de evitarlas.
En conclusión, el paradigma de programación estructurada nos enseña:
El control directo, claro y explicito de lo que hace un pedazo de código
Paradigma de programación orientada a objetos
¿Surgido de funciones?
El paradigma de programación orientada a objetos se descubrió en 1966 por Ole Johan Dahl y Kristen Nygaard cuando desarrollaban simula 67. Dos años antes de la programación estructurada y su adopción por la comunidad de programadores fue tiempo después. En 1967 se lanzó Simula 67, Simula fue el primer lenguaje de programación orientado a objetos, básicamente le agregó clases y objetos a ALGOL, inicialmente Simula iba a ser una especie de extensión de ALGOL.
Durante el desarrollo de Simula, se observó que la ejecución de una función de ALGOL necesita de ciertos datos, los cuales se podían mover a una estructura de árbol. Esta estructura de árbol es un Heap.
El heap permitía declarar variables locales de la función que pueden existir incluso después de que la función regrese un valor. ¿Esto último no les suena a lo que es closure en JavaScript?
La función padre se convierte en un constructor, las variables locales se convierten en las propiedades y las funciones hijas (anidadas) en sus métodos. De hecho este patrón es aún muy utilizado por Javacript para crear módulos y usar herencia funcional. También esto describe como funcionan las clases en Javascript, por detrás son funciones.
Esparcimiento del polimorfismo
Todo esto ayudó mucho a que se implementará el polimorfismo en los lenguajes de programación orientados a objetos. Ole Johan Dahl y Kristen Nygaard inventaron la notación:
objeto.funcion(parametro)
La ejecución de funcion
que pertenece a objeto
, pero más importante, le pasamos un mensaje a través de parámetros desde la primera parte del código a la segunda parte localizada en un objeto diferente.
La biología molecular y el paso de mensajes
Tiempo después se creó Smalltalk, un lenguaje de programación orientado a objetos mucho más sofisticado y moderno, a cargo de este proyecto estaba Alan Kay. A esta persona se le atribuye la definición formal de la programación orientada a objetos.
La principal influencia en la programación orientada objetos de Alan Kay fue la biología, él es licenciado en biología. Aunque es obvio que fue influenciado por Simula, la influencia de LISP (lenguaje de programación funcional) no es tan obvia.
Desde un inicio él pensaba en los objetos como células interconectadas en una red, de la cual se podrían comunicar mediante mensajes.
Alan Kay comentó lo siguiente:
Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es “Envío de mensajes“.
Alan Kay
Mi Ingles es malo así que pueden revisar el texto original aquí.
Mejor composición de objetos sobre herencia de clases
Smalltalk permitió también la creación de Self, creado en Xerox Parc y luego migrado a Sun Microsystems Labs. Se dice que Self es una evolución de smalltalk.
En este lenguaje de programación se introdujo la idea de prototipos, eliminando la utilización de clases para crear objetos, este lenguaje utiliza los objetos mismos para permitir que un objeto reutilice las funcionalidades de otro.
Self es un lenguaje rápido y en general se le conoce por su gran rendimiento, self hizo un buen trabajo en sistemas de recolección de basura y también utilizaba una maquina virtual para administrar su ejecución y memoria.
La máquina virtual Java HotSpot se pudo crear gracias a Self. Lars Bak uno de los últimos contribuidores de Self, se encargó de la creación del motor de Javascript V8. Debido a la influencia de Self tenemos hoy en día el motor de JavaScript V8, el cual lo utiliza node.js, mongoDB y Google Chrome internamente.
Self seguía una de las recomendaciones del libro Design Patterns: Elements of Reusable Object-Oriented Software, mucho antes de que saliera publica esta recomendación:
Mejor composición de objetos a herencia de clases.
Design Patterns: Elements of Reusable Object-Oriented Software
Ejemplo
Actualmente Javascript utiliza prototipos para la reutilización de código, se podría decir que usa composición, en lugar de herencia. Veamos un ejemplo:
const persona = {
saludar () {
return 'Hola'
}
};
// se crea un programador con el prototipo igual al objeto persona
const programador = Object.create(persona);
programador.programar = () => 'if true';
const saludo = programador.saludar();
const codigo = programador.programar();
console.log(saludo); // 'Hola'
console.log(codigo); // 'if true'
El objeto programador
se basa en el prototipo de `persona`. No hereda, solo reutiliza directamente la funcionalidad de `persona`.
JavaScript surge poquito después de JAVA, en el mismo año. JavaScript tomo influencias de Self, y a su vez Self fue influido por smalltalk.
La sintaxis de clases actuales en Javascript no es más que una fachada, bueno un poco más exacto son una evolución de las funciones constructoras, pero internamente, su base, son las funciones y los prototipos.
La comunicación con el paso de mensajes
Como ya vimos, el origen de la programación orientada objetos tiene su origen en las funciones, y la idea principal siempre ha sido la comunicación entre objetos a través del paso de mensajes. Podemos decir que La programación orientada a objetos nos habla del paso de mensajes como pieza clave para la comunicación de objetos, por ejemplo, cuando un método de un objeto invoca el método de otro.
objeto.funcion(parametro);
En conclusión, el paradigma de programación orientado a objetos, nos enseña:
El paso de mensajes para la comunicación entre objetos
Paradigma de programación funcional
Cálculo lambda
La programación funcional es el paradigma de programación más viejo, de hecho su descubrimiento fue mucho antes de la programación para computadoras, fue descubierto en 1936 por Alonzo Church, cuando invento el cálculo lambda, base del mismo problema a resolver de su alumno Alan Turing.
Como dato curioso el símbolo del cálculo lambda es:
λ
El primer lenguaje basado en programación funcional fue LISP, creado por John McCarthy. Como vimos anteriormente, Alan Kay también tomo influencias de LISP para crear Smalltalk, con esto podemos comprobar que los paradigmas de programación pueden estar unidos y su relación entre ellos surge de la idea de como resolver problemas de manera más eficiente, no están peleados, ni separados, buscan el mismo objetivo y de alguna manera evolucionan en conjunto.
La base del cálculo lambda es la inmutabilidad, más adelante revisaremos este concepto.
Similitudes con los paradigmas OO y estructurada
Como vimos en el paradigma de programación estructurada, podemos descomponer nuestros programas en unidades más pequeñas llamadas procedimientos o funciones. Dado que la programación funcional es el paradigma más viejo, podemos decir que el paradigma de programación estructurada también se apoya en la programación funcional.
Ahora bien, si analizamos un poco la notación objeto.funcion(x)
que vimos en la sección del paradigma de programación orientada a objetos, no es muy diferente de funcion(objeto, parametros)
, estas dos lineas de abajo son iguales, la idea fundamental es el paso de mensajes, como nos indica Alan Kay.
objeto.funcion(parametro);
funcion(objeto, parametro);
Smalltalk usa el “El Modelo Actor”, el cual dice que los actores se comunican entre ellos a través de mensajes.
Por otro lado tenemos a LISP, el cual tiene un “Modelo despachador de funciones” que actualmente le llamamos lenguaje funcional, estos modelos son idénticos, porque las funciones y métodos lo que hacen es enviar mensajes entre actores.
Un ejemplo de esto es cuando una función llama a otra función o cuando se invoca el método de un objeto, lo que sucede es que existen actores y se comunican entre ellos a través de mensajes.
De nuevo el paso de mensajes
Entonces podemos recalcar que la idea principal de la POO es el envío de mensajes y que no es muy diferente de la programación funcional, de hecho según lo que ya establecimos en las secciones anteriores, POO nació de una base funcional. Y aquí es importante remarcar lo que dijo Alan Kay Co-creador de SmallTalk:
Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es “Envío de mensajes“.
Alan Kay
A partir de los modelos de comunicación entre mensajes de Smalltalk y LISP, se creó Scheme.
Scheme tiene la bondad de usar Tail recursion y closure para obtener un lenguaje funcional, closure permite tener acceso a las variables de una función externa aun cuando esta ya haya regresado algún valor. Esto es el mismo principio que origino a la programación orientada a objetos a cargo de Ole Johan Dahl y Kristen Nygaard. ¿Recuerdan la pregunta de closure en Javascript?
Javascript usa mucho closure en su paradigma de programación funcional. JavaScript tomó influencias de Scheme, a su vez Scheme fue influido por LISP.
Sin efectos colaterales
La base del cálculo lambda es la inmutabilidad, por consiguiente, la inmutabilidad también es la base del paradigma de programación funcional.
Nosotros como programadores funcionales debemos seguir este principio y limitar las mutaciones de objetos lo más que se pueda para evitar efectos colaterales.
Repito, la idea principal de la programación funcional es la inmutabilidad, la cual te permite crear tus programas con menos errores al no producir efectos colaterales difíciles de controlar.
Como ejemplo, supongamos que tenemos un objeto persona
y queremos “cambiar” el nombre, lo más obvio seria modificar la propiedad nombre directamente, pero que pasa si ese objeto es utilizado en otra parte y se espera que nombre sea “Jaime”, es por eso que en lugar de cambiar la propiedad nombre, solo creamos un nuevo objeto persona con diferente nombre, sin modificar el original.
function cambiarNombre(nombre, persona) {
return {
nombre: nombre,
edad: persona.edad
}
}
const jaime = { nombre: 'Jaime', edad: 30 };
const juan = cambiarNombre('Juan', jaime);
console.log(jaime); // { nombre: 'Jaime', edad: 30 }
console.log(juan); // { nombre: 'Juan', edad: 30 }
Aquí encontrarás más detalles sobre los principios fundamentales de programación funcional:
Introducción a programación funcional
Por último, el paradigma de programación funcional nos enseña:
Sin efectos colaterales, para una expresión clara y menos propensa a errores
Conclusión
Es importante destacar que si los tres paradigmas de programación fueron implementados entre los años 1958 con LISP (programación funcional) y Simula (programación Orientado a objetos) en 1966, y el descubrimiento del paradigma de programación estructurado en 1968, en solo un lapso de 10 años existió la innovación de los paradigmas de programación, y realmente nuevos paradigmas no han surgido, al menos que no sean basados en estos tres principales.
Ahora mismo ya han pasado más de 60 años, lo que nos dice de la importancia y la firmeza que tienen a pesar del tiempo transcurrido.
- Paradigma de programación funcional (1958, ya implementado en un lenguaje de programación para computadoras, recordemos que fue descubierto en 1936)
- Orientado objetos (1966)
- Estructurada (1968)
La implementación de estos tres paradigmas desde el inicio en el interior de JavaScript han hecho que este lenguaje de programación tenga el éxito global de hoy en día. Y nos comprueba lo valioso de combinar los paradigmas al escribir nuestros programas.
JavaScript es en lenguaje de programación multiparadigma, por lo que se puede combinar los paradigmas para crear un código mucha más eficiente y expresivo usando:
- El control directo, claro y explicito de lo que hace un pedazo de código
- El paso de mensajes para la comunicación entre objetos
- Sin efectos colaterales, para una expresión clara y menos propensa a errores
Referencias
https://en.wikipedia.org/wiki/Structured_programming
https://en.wikipedia.org/wiki/Structured_program_theorem