Este es el primer post de una serie donde vamos a aprender de forma divertida y práctica los beneficios que aporta la programación funcional . Vamos a explicar sus conceptos fundamentales y el por qué son tan importantes. ¡Que nadie se asuste! La teoría está muy bien, pero como desarrolladores que somos también implementaremos una librería en Java donde pondremos en práctica todo lo aprendido.

La librería se llamará JIO, iniciales de Java Input Output. Está basada en el IO monad, concepto introducido en Haskell por el gran Philip Wadler, también conocido como Super Lambda Man. Si estás familiarizado con Scala o Kotlin y la programación funcional, encontrarás alguna similitud con librerías como ZIO, Cats y Arrow. No obstante, hay muchos aspectos donde JIO será única.

Vamos a comunicarnos utilizando dos vías. Para el desarrollo de la librería utilizaremos GitHub. Los desarrolladores, sin importar lo cerca que estemos físicamente, siempre tenemos que utilizar GitHub o una plataforma similar para comunicarnos. Dudas, recomendaciones, bugs, lo que sea, tenemos que abrir una issue. Las explicaciones teóricas y prácticas se realizarán en los posts.

Si quieres, puedes sugerir vía GitHub e incluso desarrollar nueva funcionalidad. De esta forma, todas las personas que os impliquéis apareceréis como coautores de la librería 😎. Siempre he sido defensor de una de las ideas que Mark Zuckerberg expuso en The Hacker Way: "Code should win arguments". ¿Aceptas el reto? ¡Claro que sí!

Cualquier pregunta, sugerencia o inquietud relacionadas con el contenido de los post, no dejéis de escribirla en los comentarios. ¡Bienvenidos!

¿Qué conocimientos necesito para seguir el curso?

Solo se requieren conocimientos de Java.

No soy fan de ningún framework y no utilizaremos ninguno. ¿Cuál es la diferencia entre un framework y una librería? Los frameworks hacen uso del código fuente de nuestras aplicaciones, mientras que nosotros hacemos uso del código fuente de las librerías.

Quién tiene el control es la diferencia fundamental. ¡Y yo prefiero que lo tengamos nosotros! Además, los frameworks son difíciles de optimizar y normalmente muy ineficientes. Han llegado a unos niveles de abstracción demasiado complicados.

Dedica cuatro minutos a la exposición que Rich Hickey (creador de Clojure) hace en este vídeo, donde habla de la complejidad en la que ha deteriorado la programación orientada a objetos. ¿Qué te ha parecido? ¡Yo cada vez que lo veo me parto de la risa!

¿Qué vamos a aprender?

En plena era de los microservicios es común tener que desarrollar flujos muy complejos como el siguiente:

Programación Funcional 1

Cada círculo puede ser una petición HTTP, un acceso a una base de datos, etc. Es de vital importancia disponer de una solución para abordarlo y testearlo de forma simple, a ser posible en una línea de código 😲. Con JIO aprenderemos cómo hacerlo. Además, el hecho de que distintas tareas se ejecuten en paralelo o de forma asíncrona no debe suponer mayor complejidad.

Otro aspecto muy importante que vamos a aprender es a trabajar con los errores y distinguirlos de las excepciones, lo que es crucial para diseñar un buen API. Las excepciones son un tipo de dato más y deben de considerarse desde el inicio si se quiere implementar un sistema robusto.

En programación funcional las excepciones, como cualquier otro tipo de dato, se retornan y nunca se lanzan con la esperanza de ser capturadas. La regla de oro para familiarizarte con todos los posibles fallos y construir sistemas robustos es desconectar todo (el router, balanceador, base de datos, gateway etc.) y observar cómo se comportan tus aplicaciones. Haz fallar los sistemas, no esperes a que fallen (y seguro que fallarán 😰).

En el contexto de la programación reactiva, y muy relacionado con los errores, es imprescindible para diseñar sistemas resilientes a errores realizar reintentos ante determinados fallos como particiones de red, cierres de conexiones TCP, etc. Existen distintas políticas. Se puede reintentar un determinado número de veces, durante un tiempo máximo, o políticas más sofisticadas como Exponential Backoff y Jitter.

Desarrollando JIO utilizaremos las características más importantes de Java relacionadas con la programación funcional y el diseño de APIs. Desde Java 5 y Generics hasta Java 17 y pattern matching.

Por último, me gustaría enfatizar lo difícil que resulta diseñar e implementar un buen API. Seremos muy exigentes al respecto. Como siempre, tener como referencia a los mejores es un acierto, y respecto al diseño de APIs no puedo dejar de recomendar las sugerencias que Joshua Bloch nos da en How to design a good API and why it matters. Las tendremos muy presentes. ¡Joshua Bloch se deja ver poco y no hay que desaprovechar las pocas oportunidades que nos da!

¿Por qué la programación funcional recuperó la atención de los lenguajes más comerciales?

Hubo un tiempo donde a la hora de hacer un programa más eficiente existía la opción de simplemente esperar. ¡Vaya chollo! Se sabía que en breve saldría al mercado un procesador con mayor frecuencia de procesamiento.

Pero llegó el día, a principios de siglo XXI, donde aumentar la frecuencia suponía que los dispositivos se calentaran en exceso. La solución fue aumentar el número de procesadores. ¿Pero dado un programa, si duplico el número de procesadores, el programa se ejecuta el doble de rápido?

La respuesta es tajante: no (a no ser que se diseñe con ese propósito). Aquí es donde entra en juego la programación funcional. Es un paradigma que facilita diseñar programas que cumplan dicha propiedad al simplificar enormemente la programación concurrente y en paralelo.

Si estás interactuando con algún tipo de hardware o haciendo System Programming, paradigmas imperativos con lenguajes como C, Java,C++ o el más moderno Rust son más convenientes.

Pero en los niveles de abstracción que normalmente nos movemos los desarrolladores de aplicaciones, la programación funcional tiene grandes beneficios como mayor expresividad, código más fácil de entender y menos bugs. Es el objetivo de este curso explicar el por qué de estos beneficios y ponerlos en práctica desarrollando JIO.

¿Pero alguien utiliza lenguajes funcionales puros como Haskell?

Muchos lenguajes funcionales tienen fama de tener poca adopción. La realidad es que cuanto más aplicaciones hay escritas con lenguajes imperativos como Java, C++ y Python, más se exige a las universidades dedicarles una mayor atención para cubrir la gran demanda que hay y se olvidan otros lenguajes más beneficiosos desde un punto de vista educativo. Es una pescadilla que se muerde la cola.

Pues bien, ¡presta atención que esto es interesante! Las plataformas descentralizadas de blockchain y smart-contracts han cobrado una grandísima importancia. Ethereum es una de las más utilizadas, siendo su criptomoneda nativa ETH. Es la criptomoneda con mayor capitalización de mercado después de BitCoin.

Para hacernos una idea, Ethereum ya ha superado en capitalización a grandes empresas como PayPal. Su líder es Vitalik Buterin,consagrado hace poco como el crypto millonario más joven del mundo. Otros de sus creadores originales como Gavin Good y Charles Hoskinson, abandonaron el proyecto por distintas discrepancias y empezaron otras implementaciones.

Gavin Good creó Polkadot, siendo la criptomoneda DOT, y Charles Hoskinson Cardano, siendo la criptomoneda ADA.

A día de hoy, a pesar de que todavía no han desplegado su mainnet, tanto DOT como ADA se encuentran en el TOP 10 de las criptomonedas. La que está generando mayor expectación es sin duda ADA.

Tiene como objetivo, entre otros, instaurarse en el continente africano para proporcionar un sistema financiero más justo y descentralizado. Pues bien, Cardano está implementada en Haskell.

¡Toma ya! Que hayan elegido Haskell para implementar Cardano me parece algo increíble y pone de manifiesto la importancia de la programación funcional. Animo al lector a leer el artículo Why Cardano chose Haskell, and why you should care.

JIO en dos líneas de código

Veamos un ejemplo de un simple programa en JIO. Pide al usuario por consola dos números enteros e imprime el resultado de la suma por pantalla. Os animo a resolver este programa siguiendo un enfoque imperativo y observar las diferencias.

IO<Integer> askForNumber = ConsoleIO.printLine("Type a number: ")
                                    .flatMap($ -> READ_INT);

IO<Integer> program = PairExp.seq(askForNumber,
                                  askForNumber
                                 ) 
                             .map(pair -> pair.first() + pair.second());

Integer sum = program.join(); //execs the program

Si el usuario comete un error y no introduce un carácter numérico, el programa terminará retornando un error (contenido en IO). Vamos a introducir reintentos: un máximo de tres. Tras el primer error espera un segundo, tras el segundo dos y así sucesivamente. Pan comido:

RetryPolicy policy = RetryPolicies.incrementalDelay(Duration.ofSeconds(1))
                                  .limitRetries(3)

IO<Integer> askForNumber = ConsoleIO.printLine("Type a number: ")
                                    .flatMap($ -> READ_INT)
                                    .retry(policy);

Por claridad, hemos especificado los tipos, pero como vamos a utilizar Java 17, podemos omitirlos, ya que el compilador es capaz de inferirlos si utilizamos var para declarar las variables:

var policy = RetryPolicies.incrementalDelay(Duration.ofSeconds(1))
                          .limitRetries(3)

var askForNumber = ConsoleIO.printLine("Type a number: ")
                            .flatMap($ -> READ_INT)
                            .retry(policy);

var program = PairExp.seq(askForNumber,
                          askForNumber
                         ) 
                     .map(pair -> pair.first() + pair.second());

No está nada mal para ser Java, ¿verdad? Es muy complicado desarrollar programas tan concisos, declarativos y resilientes a errores sin seguir un enfoque funcional. Yo pasé mucho tiempo intentándolo sin éxito, hasta que un día descubrí la programación funcional y me di cuenta que a veces el problema está simplemente en uno mismo y en el cómo está intentando resolver los problemas. No es sólo una cuestión de experiencia y buenas prácticas. También es cuestión de estar utilizando el paradigma adecuado.

¿Cuánto tiempo necesito para aprender programación funcional?

Depende de hasta dónde se quiera llegar y del lenguaje de programación que se utilice, ya que hay conceptos que requieren de un type system y por tanto no se pueden aplicar en lenguajes dinámicos. Voy a contar el camino que recomiendo.

Para cualquier programador, especialmente de Python, recomiendo realizar el curso Design of Computer Programs. Tendrás el privilegio de ver a Peter Norvig programando y resolviendo problemas increíbles como The Zebra Puzzle.

Peter Norvig, aparte del director de investigación de Google y un profesor de renombre, es uno de los mejores programadores del mundo. Peter empleaba Lisp, que es el primer lenguaje funcional de la historia, creado por Jhon McCarthy, quien por otro lado acuñó el término de inteligencia artificial.

Dada la complejidad que suponía a sus estudiantes aprender y entender Lisp, decidió utilizar también Python en sus libros y cursos, como indica en este artículo.

Tuve la suerte de hacer el curso cuando no se podían consultar las soluciones hasta pasada una semana, y siempre quedaba impresionado de la belleza y expresividad de las de Peter. Parecía que utilizábamos lenguajes distintos. Fue pasado mucho tiempo cuando me di cuenta de que tenía un estilo funcional.

Hay que tener en cuenta que cuando hice el curso no sabía nada de programación funcional. Como dijo Steve Jobs, en su ya histórico discurso en Stanford: “uno da sentido a las cosas que hace en la vida y conecta los puntos mirando para atrás, y nunca mirando para adelante”. ¡Este fue mi primer contacto con la programación funcional y yo sin saberlo!

Para un programador de Java, Scala o Kotlin, recomiendo estudiar Haskell (en la bibliografía aparecen dos libros muy buenos). Cuando se lleva mucho tiempo programando utilizando unas ideas y convenciones y se aprende un paradigma nuevo siempre se intenta relacionar todo con lo que uno conoce y cuesta dejar de utilizar los vicios adquiridos.

Con Haskell es imposible programar siguiendo un estilo imperativo. No se puede utilizar ni un bucle for ni cualquier tipo de statement, y por supuesto, cualquier effect se modela con un monad. Una vez leído y entendido los conceptos fundamentales de Haskell, el resto de lenguajes te parecerán sencillos. Conviene decir que todo cambio de paradigma resulta complicado en los inicios, caso contrario, es que se está realizando un simple cambio de sintaxis y no de mentalidad.

Sea cual sea el camino que elijas, no tengas prisa. Cómo indica nuestro ya amigo Peter en Teach Yourself Programming in Ten Years, evita cualquier recurso que te prometa aprender algo en 24 horas.

Y, recuerda, ningún lenguaje de programación ni paradigma es mejor ni peor que otro de forma generalizada. Sí que es cierto que resolviendo ciertos problemas unos brillan más que otros. ¡Descubramos juntos donde brilla la programación funcional!

Conclusiones

Este es el primer post de lo que pretende ser un curso de programación funcional de carácter teórico y práctico. Podréis poner a prueba los conocimientos adquiridos e ir implementando la librería JIO.

Si bien los conceptos teóricos que se adquieran son extensibles a cualquier lenguaje de programación, nos centraremos en Java y su evolución, para adquirir conocimientos de sus mejoras más importantes en las últimas releases.

Bibliografía

  1. Programming in Haskell por Graham Hutton
  2. Haskell, the craft of functional programming por Symon Thomson (más avanzado)
  3. Rich Hickey rants about HttpServerRequest por Rich Hickey
  4. How to design a good API and why it matters y A Brief, Opinionated History of the API por Joshua Bloch
  5. Design of computer programs (Python) por Peter Norvig
  6. The Hacker Way por Mark Zuckerberg
  7. Exponential Backoff And Jitter por AWS Architecture Blog
  8. Why Cardano chose Haskell, and why you should care por Cardano-Foundation
  9. Teach Yourself Programming in Ten Years por Peter Norvig
  10. Python for Lisp Programmers por Peter Norvig
  11. Charles Hoskinson - Why Haskell for Cardano? | Forkast.News
  12. Introducción a chaos engineering por Román Martín y Alberto Grande
  13. Netflix y chaos engineering

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.