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