sábado, 14 de marzo de 2015

El boom de la Programación Funcional (parte 2/3)

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