Esta es la segunda parte de una trilogía de posts con desvarios 
propios y ajenos relacionados a la programación funcional. En la primera, hice una pequeña introducción intentando convencerlos de lo 
útil e interesante que es esto, pero sin decir nada en realidad. Y conté
 luego cual fue mi primer acercamiento casual a la misma, remontándome 
gracias a la Internet 30 años atrás. En esta segunda parte, volvemos a 
la actualidad, y les cuento cómo es que en estos últimos años se ha 
vuelto más importante.
Herb Sutter ya observaba muy bien las tendencias del hardware 10 años atrás y ya anunciaba que el "almuerzo gratis había terminado" (excelente artículo que lleva ese nombre),
 haciendo referencia a que los procesadores ya no iban a aumentar su 
velocidad, sino que iban a tener que aumentar su número
 para darnos más capacidad de cálculo. Y se cumplió, hasta los teléfonos 
ahora tienen más de un núcleo. Pero, una mayor velocidad de reloj y/o un mayor ancho de banda en los buses hace que nuestros programas corran más rápido sin 
esfuerzo de nuestra parte, mientras que para aprovechar un mayor número de cores los programadores sí tenemos que adaptarnos. Esta tendencia puso en la mira durante la
 última década a la computación paralela (donde un programa utiliza 
varios procesos o hilos que se ejecutan en simultáneo, "cooperando" para
 lograr su objetivo). Y de la mano de la computación paralela vino la 
programación funcional. Otro de mis favoritos, el grandísimo John 
Carmack también lo advertía en este otro muy interesante artículo.
¿Y
 cómo se relaciona una cosa (paralelismo) con la otra (programación 
funcional)? La relación parte en realidad de una especie de ataque al paradigma de programación predominante en el mercado: la orientación a objetos. La 
programación en paralelo presenta como desafío adicional la 
"sincronización". Dos hilos de ejecución que trabajan con los mismos 
datos, deben tener cuidado de no modificarlos al mismo tiempo, porque 
eso puede generar errores. Es decir, ¿qué pasa si (por ejemplo) un hilo consulta un 
dato mientras el otro está modificándolo, y ve entonces un estado 
intermedio, posiblemente inválido? (busquen race-condition, o data-race si no saben de qué hablo). Entonces, hay que sincronizar el acceso: 
algo así como poner un semáforo cerca del dato, para que los hilos pasen
 de a uno. Y esto reduce el paralelismo, ya que no tiene mucha utilidad 
que un hilo se la pase esperando la luz verde la mayor parte del tiempo. Así
 que, compartir datos entre hilos, es una de las principales fuentes de 
problemas en arquitecturas paralelas.
Entonces, 
volviendo a la programación orientada a objetos, aquí el principio de 
ocultación nos puede jugar en contra. Porque un objeto que oculta su 
estructura interna, está ocultando cuales son sus datos (propios y 
compartidos con otros objetos), propiciando la aparición de este tipo de complicaciones al usarlos en paralelo. Entonces, la ocultación oculta 
detalles que en la programación paralela son realmente importantes para
 saber donde es necesario agregar mecanismos de sincronización. Pero... 
¿que tal si el objeto tiene sus mecanismos de sincronización internos, 
transparentes? En ese caso, no generaremos problemas por falta de 
sincronización, sino por exceso. Ya dije que la sincronización es 
necesaria, pero en esencia se encarga de anular el paralelismo, hay que evitarla siempre que se pueda. Si la 
ocultamos dentro de los objetos, al componer un programa a partir de 
varios objetos (otra premisa fundacional de la POO), estamos reduciendo enormemente su eficiencia 
paralela, sin que se note en el código, sin que sea simple de analizar y
 diagnosticar, y en muchos casos sin que podamos hacer nada para 
solucionarlo. El problema está oculto, y oculto adrede. Y no solo eso, pueden aparecer problemas aún peores, como los temidos deadlocks (dos hilos bloqueados esperando cada uno algo del otro para avanzar).
Por
 esta clase de cosas se buscan alternativas que permitan analizar los 
programas de otra forma, y que o bien eviten o bien hagan obvios estos 
problemas. Y el paradigma funcional es ideal para esto. Tiene una base 
matemática muy sólida, permite la composición sin problemas implícitos (no hay side-effects),
 y facilita el análisis y seguimiento de las implementaciones. Pero nos 
obliga a pensar distinto, y nos quita o limita algunas herramientas 
básicas en otros paradigmas (como los atributos compartidos, las infames variables globales, o hasta la inocente asignación), sin las 
cuales al principio creemos que no vamos a sobrevivir. 
Pero
 no hay que sacar conclusiones apresuradas. No quiero que se lleven la 
idea de que ahora la programación orientada a objetos es mala, o algo 
así. Ni que el paradigma funcional es la panacea. Hay que conocer un 
poco de ambos mundos, para identificar o (mejor aún) prevenir los 
problemas, y aprovechar lo mejor de cada lado. Es decir, conocer las 
herramientas y tener criterio para saber cuando y cómo utilizarlas, 
tarea esencial de un buen ingeniero. Yo por mi parte comencé a utilizar ideas del mundo funcional para diseñar o analizar 
partes de mis programas, que siempre viven en el mundo de los objetos. Y encima, desde 2011 en adelante, mi lenguaje favorito, C++, convirtió virtualmente a las funciones en ciudadanos de primera clase. Esto nos agregó varias otras herramientas sintácticas para facilitar la programación funcional. Pero ¿qué significa realmente?, y ¿cómo funciona? Ya se hizo largo, lo dejo para tercera parte.
 
 
 
No hay comentarios:
Publicar un comentario