
Una de las cosas que me llamó la atención es que la cantidad de posts que te llevan a definiciones vagas u opiniones que se encuentran totalmente alejadas de la realidad abundad en internet sobre este tema, todos muestran el mismo ejemplo de la instrucción for y dicen exactamente lo mismo: Programación declarativa es cuando declaras lo que quieres hacer y no le dices como hacerlo. El problema de esta definición es que te lleva a la interpretación, y con la interpretación llega la opinión, y es en ese preciso momento donde el sentido que tiene el desarrollo, de ser una ciencia exacta, se pierde. No quiero ser mal interpretado con esto, las opiniones son válidas y tú puedes preferir un estilo sobre el otro, lo que sencillamente no tolero es inventar definiciones. Me he llevado sorpresas del tipo: Un if es lo más declarativo que existe, ya que declaras que quieres que el código vaya por un lado o por el otro. Lo anterior es, académicamente, incorrecto. Programación declarativa y programación imperativa tienen una definición muy establecida y fácil de entender que no debería dar espacio para interpretaciones. Y en esta web vamos a ver esto.
Partamos por la más sencilla, la que te enseñan en todos los tutoriales de internet y también en las universidades:
Que es la programación imperativa?
Es un paradigma de programación, está enfocada en cómo se ejecuta el código, define el control de flujo de manera explícita y cambia el estado de una máquina.
Ahora vamos parte por parte, la oración de esta enfocada en cómo se ejecuta el código está abierta a la interpretación y le puedes dar la definición que quieras en base a que sea para ti como se ejecuta el código. Y por el solo hecho de ser ambigua la detesto. Sin embargo, todo lo que viene después no es ambiguo ya que está hablando sobre conjunto de operaciones que se realizan en los lenguajes de programación. Cuando se dice que define el control de flujo tenemos algo bastante detallado a que se refiere. Si buscamos en internet, lo cual no es necesario ya que te lo diré acá, podemos ver de qué control de flujo se refiere a algo muy específico:
Definición de variables
Todo let, const o var es imperativo, cada vez que defines una constante estas definiendo control de flujo y, además, estás mutando el estado de la máquina, este puede ser después recolectado por el garbage collector, sin embargo sigue siendo una mutación que se encuentra en la memoria de la máquina y además define una variable que será utilizada más adelante para definir el flujo.
Decisiones
Todo if, else, else if, switch o instrucción que te permita a ti tomar una decisión en base a algo que ya definiste es considerado imperativo.
Loops
Así es, este aparece en todos los ejemplos de internet, todo loop controlado, ya sea for, while, do while, entre otros, donde donde en base a una condición debamos detener este loop, es considerado imperativo, incluso los foreach donde de manera explícita estemos indicando que vamos a iterar un arreglo, no así map, filter y reduce ya que son conceptos matemáticos de aplicar función, filtrar y finalmente fold, sobre esto hablaremos más adelante cuando veamos Monads. En definitiva, cualquier loop controlado, infinito, con condición de salida, sin condición de salida, break o continue son considerados imperativos.
Manejo de excepciones con try catch
Cuando definimos de manera explícita un try catch, estamos definiendo de manera imperativa por qué lado de nuestro código vaya la ejecución de este.
Async
Así es, nuestra querida función de Async await también es considerada imperativa.
Generadores
Funciones generadoras también son consideradas parte del control de flujo y, por ende, imperativa.
Luego de haber visto esto lo más probable es que me estes odiando o estes de acuerdo conmigo, sin embargo, déjame decirte que todo lo escrito anteriormente no refleja mi opinión si no la misma definición que puedes encontrar en papers históricos que hablan sobre los primeros computadores y lenguajes de programación. Estamos hablando de que lo más probable es que hayas estado escribiendo código imperativo creyendo que este es código declarativo. Ahora que ya sabemos que es control de flujo nos queda ver la parte de «Cambia el estado de la máquina», sin embargo, esto ya es más sencillo de ver.
Cambia el estado de la máquina
Con esto nos referimos a la declaración de variables, estas vas a modificar el estado de la máquina, y es aún peor si definimos variables globales ya que estas mutan el estado de la maquina y esta mutación puede perdurar durante bastante tiempo, años inclusive.
Programación declarativa
Ya definido el control de flujo y la programación imperativa ya podemos pasar a ver la definición de la programación declarativa:
Define la lógica de lo que se va a ejecutar, pero sin indicar como ni un detalle del control de flujo.
Esta definición, luego de que ya sabemos lo que es control de flujo, es muy poderosa, ya que te permite darte cuenta de que, si has escrito un if, pensando que es declarativo, no estabas en lo correcto, la programación declarativa se ve muy parecida a un «tubo», donde se recibe una entrada y esta escupe una salida, y puedes hacer código declarativo de múltiples maneras, ya sea escribiendo solo middlewares, composición de funciones como vimos un capítulo anterior, con clases, etc. Nosotros no utilizaremos las clases, pero si haremos código más declarativo utilizando composición o middlewares.
Un ejemplo de programación declarativa:
// la siguiente función es como compose, pero nos permite
// indicar un método con el cual vayamos a encadenar
// nuestras funciones
const composeM = method => (...ms) => (
ms.reduce((f, g) => x => g(x)[method](f))
);
// con esto podemos componer promesas!
const composeP = composeM('then');
const handleUserCreate =
composeP(
saveInDatabase(UserModel),
validateWithSchema(userSchema),
)
const app.post('/users', (req, res) =>
handleUserCreate(req.body)
.then(sendSuccess(res))
.catch(sendError(res))
)
En el ejemplo de arriba vemos cual es la intención del código escrito:
- Tomamos el objeto de request y se lo entregamos a nuestra función handleCreateUser.
- La función valida la petición contra un esquema
- Luego guardamos en la base de datos con el modelo del usuario.
- Finalmente enviamos los datos al cliente o enviamos el error en el caso de que exista.
Si se fijan, el detalle de cómo esta implementado esto no lo vemos, tampoco nos interesa, lo que nos interesa es la intención del código. Sabiendo la intención del código podremos ver si existe algún error en nuestra lógica y, lo que finalmente nos interesa que se encuentre con tests, son estas pequeñas funciones que hemos creado donde nuestro detalle queda abstraído de nosotros. No hay if, try catch, for, async await, solo composición de funciones. Esto podría ser generalizado aún más y así poder reutilizar más nuestro código, incrementando nuestra velocidad y calidad en el largo plazo.
Así que la próxima vez que quieran escribir una funcionalidad, donde quieran plasmar la lógica de lo que se está escribiendo, primero denles nombre a las funciones, definan paso a paso que es lo que quieren que haga su código, las dependencias que este puede necesitar utilizando currying, aplicación parcial o closures y, cuando el orden y lo que se tenga que ejecutar les haga sentido, luego implementen el detalle, o incluso mejor, los tests y luego el detalle.
Comments (1)
Mis libros favoritos de programación – Hola Mundosays:
enero 31, 2023 at 12:09 pm[…] Este libro pasa lo básico en cuanto a JavaScript y empieza a enseñarte conceptos para hacer tu código más fácil de reutilizar y más fácil de testear. La programación funcional puede utilizarse perfectamente en conjunto con el paradigma orientado a objetos y la combinación de ambos hace que resolver problemas sea bastante fácil, además que siempre se agradece tener un código declarativo que es fácil de entender. Si no sabes que es código declarativo te dejaré un video donde podrás ver a qué me refiero, el video es algo viejo, pero creo que se entiende la diferencia entre código declarativo y código imperativo. La diferencia podría sorprenderte, si quieres saber más de este tema, te invitamos a leer nuestro post: Programación imperativa vs programación declarativa […]