Pues parece que será F#... ¿O tal vez no?
Llevo muchísimo tiempo queriendo hacer un proyecto propio. No me faltan ideas. Aunque confieso que la mayoría se presentan como formas de hacer distintas a como se decide hacer en el trabajo. Y con las que muchas veces no estoy de acuerdo, también reconozco. Motivo por el que buena parte de esas ideas pasa por hacer lo mismo, pero de forma diferente o con prácticas distintas.
Cada vez que me pica una idea, y aparece la intención, acabo perdiéndome en un sinfín de dudas de si «será mejor hacerlo con éste o mejor usar éste otro». Porque, para mi desgracia, me gustan muchos lenguajes y las cosas que proponen algunos me seducen intelectualmente. Aunque es cierto que, poco a poco, voy acotando, aún no he conseguido decidirme completamente. Por ejemplo, han pasado por mi cabeza lenguajes como Clojure y Scala, pero que al final por un motivo u otro, no terminan de satisfacerme completamente; dejando libros en mi biblioteca que no creo que vuelva a abrir nunca.
Clojure me atrajo porque nunca había trabajado con sintaxis Lisp alike. Hay cosas que propone el lenguaje que me llamaban mucho la atención. Aunque al final he optado porque el lenguaje sea sí o sí fuertemente tipado y, muy importante, con tipado estático. Esto, que es irrenunciable, deja algunos otros lenguajes interesantes fuera; como Erlang (y su OTP) o su primo hermano llamado Elixir. A estos simplemente me he asomado por curiosidad y hay muchas cosas que desconozco, pero tomé la decisión de regir mi vida programática por los tipos.
Scala parecía ser la alternativa hasta que apareció Kotlin en mi vida. Y lo hace porque mis últimos seis o siete años laborales han venido marcados por la programación —más mantenimiento que código nuevo— en Java, el 95% del tiempo. El otro 5% se reparte, ordenados de mayor a menor tiempo de dedicación fuera de Java, entre Groovy, el que más, JavaScript, TypeScript, Kotlin, y algo de Python. Todo eso sin tener en cuenta otros lenguajes tipo Bash o los de la familia de «Infrastructure as Code» como Terraform y Ansible, que para mí entran en la categoría de utilería inevitable; pese a que al final dedico buena parte del tiempo laboral en ellos.
Kotlin es un lenguaje que me encanta. Es un Java mucho mejor que Java. Pero no puedo usarlo más porque no me dejan. Literalmente. He empezado algunos proyectos/prototipos en Kotlin que he tenido que reescribir en Java 8 porque, simplemente, el responsable o el equipo no quieren aprender «cosas nuevas». Bastante frustrante, por cierto. Frustra porque esos mismos argumentos no aplican cuando quieren meter, a veces con calzador, «rellena aquí lo que a mí sí me gusta».
Groovy, por otro lado, es un lenguaje que me gustaba al principio y que, con el paso del tiempo, y peleándome con su Duck typing, he acabado dejando de apreciar. Ahora, cada vez que tengo que revisar código escrito por otros y buscar dónde puede estar el fallo de un «has no attribute» o similar recuerdo por qué hace mucho decidí que, en la medida de mis posibilidades, solo usaría lenguajes estáticos y fuertemente tipados. Lo mismo pasa con Python. Si puedo evitar usarlo en el futuro, lo evitaré. Al menos para nada que no sea scripting. Y no tengo pensado dedicarme al Machine Learning en breve, sector en el que parece brillar de forma merecida. Y para scripting ya vengo usando F# cuando puedo.
TypeScript, por otro lado, es un lenguaje que no me deja indiferente, pero al que no he podido —o no he querido— dedicar más tiempo del estrictamente necesario para salir del paso y resolver el problema puntual de mantenimiento. Y desde mi ignorancia creo que F# con Fable resolverían el mismo dominio de solución: Transpilar a JavaScript y ejecutar en el navegador.
Continuando con Kotlin repito que me gusta como lenguaje de programación. Mucho. Aunque si lo comparo con F#, le queda mucho, pero mucho, mucho para seducirme en la forma en que lo hace el heredero de OCaml. Y es que cuando descubrí los Tipos de Datos Algebraicos, Discriminated Unions en términos de F#, y cómo los representan/soportan lenguajes como F#, OCaml y Haskell, me dije «sí, esto es lo que andaba buscando». Así que esas Sealed Classes ofrecidas por Kotlin nunca serán lo mismo. Pero ahí están.
Otro motivo por el que descarto Kotlin en particular, y la JVM, en general, se debe a que es mi día a día laboral. Yo crecí profesionalmente con C# y a él vuelvo —o volvía— siempre que pienso en hacer algo propio. Más ahora que en la CLR está F#. (Bueno, hace ya una década que existe). Mi transformación a JVM fue algo puramente accidental. O más bien vino forzada por el mercado laboral. Y eso que reconozco que la JVM es una obra de ingeniería apabullante con un éxito más que merecido. En sí misma la JVM, más allá del principal lenguaje de programación que soporta, es una gozada. Hay aspectos internos muy interesantes de cómo resuelve cosas como el JIT, o el propio recolector de basura, que merecen un estudio dedicado. Para el que le interese, al menos. Pero, para mi gusto, ha tardado mucho en acoger ciertas facilidades lastrada por su lenguaje estrella. En cualquier caso, no deja de ser un tema de gustos. Y para mi gusto, da igual lo buena que sea la capa de abstracción sobre la que ejecutas el programa, lo importante es que el lenguaje te facilite la comunicación de las ideas que quieres plasmar. Java hace mucho tiempo que se volvió un lenguaje pesado de escribir y muy limitado a la hora de gestionar tipos de datos.
Por cierto, ETA y OCaml Java fueron mis candidatos predilectos hace unos años para aprovechar lo aprendido con la JVM en mi tiempo de trabajo. Pero ambos proyectos/lenguajes han quedado en el limbo, desgraciadamente. Frege, una suerte de Haskell reducido, fue otro que me atrajo. Pero en mis primeras pruebas (creo que por 2016) tuve no pocos problemas de rendimiento y, al final, lo dejé de lado. Aclaro que probablemente los problemas sean más achacables a mi inexperiencia que al lenguaje, ojo. Pero después de unos días peleando en mi tiempo libre para resolver un tema de concurrencias me di por vencido.
Pero no está siendo sencillo decidir con qué lenguaje meterme en la cama. F# me gusta. Mucho. Pero Haskell me gusta mucho más. Aunque veo que es más por un tema de formalismos que por su vertiente práctica. Es como decir que me gustan las Matemáticas —que me gustan y mucho— y por eso quiero usarlas para todos y cada uno de los aspectos de mi existencia. Cuando en realidad me basta con usar una simple regla de tres o un sencillo cálculo en lugar de montar un sistema no lineal de ecuaciones diferenciales para determinar una solución concreta. ¿O es que voy a plantear la elección de un pantalón basándome en aplicar la Teoría de la Decisión? Sería agotador. Igual el símil está cogido por los pelos, pero espero que sirva para hacer entender el sentimiento. Para mí Haskell sería el ideal de belleza a alcanzar para transformarme en EL MEJOR programador que pueda llegar a ser; pero en el día a día soy más ágil —y percibo muchas más facilidades— con F#. O eso creo. Salvo dos o tres scripts, poco más he hecho hasta la fecha. «¿Pero y si pudiera usar Las Matemáticas para todo y usarla todos los días…?» es un pensamiento que me asalta cada dos por tres.
Otra cosa de esas que me echa siempre para atrás al —intentar— usar Haskell es el abuso, a veces aparentemente absurdo, de los pragmas del lenguaje. Prácticamente no se puede hacer nada interesante sin recurrir a varios de ellos una y otra vez. Mucha pericia y muchas horas perdidas para recordar qué toca poner. Y, para mí mucho más importante, el por qué. No obstante, y dicho lo anterior, recomiendo mucho, mucho, mucho, dedicar tiempo a familiarizarse con conceptos del —y con el propio— lenguaje. Creo que siempre será tiempo bien invertido. Learn You a Haskell for Great Good! está muy bien para adentrarse en el mundillo de los Aplicativos, los Funtores, los Monoides y las Mónadas.
Jugueteando con Haskell y los sistemas de tipos acabé leyendo el libro Type-Driven Development with Idris. Lo que he leído/probado de Idris es muy atractivo/interesante. Aunque no he dedicado más tiempo de la lectura del libro y alguna prueba sumamente simple que no terminaba de compilar y/o de funcionar bien. Y mucho menos me lo he planteado como alternativa seria para hacer algo concreto. De meterme en ese mundillo, supongo que lo haría de la mano de F*. Aunque no tengo el tópico de los tipos dependientes en mis have to del lenguaje a elegir. Sería aún más estricto que Haskell, creo. Y ya he dicho que Haskell lo dejo de lado por ser excesivamente formal 😎.
Durante unos días le estuve dando a OCaml y tenía buena pinta. Bueno, la sigue teniendo. Aunque luego lees aquí y allí, buscas información de esto y de aquello y da la impresión de que al final lo están usando cuatro pelagatos y que es un lenguaje muy de nicho. Sé que no es cierto, pero comparando la comunidad del lenguaje con la de Haskell, creo que hay mucha diferencia. Y si Haskell lo concibo como el lenguaje ideal, no dedicaría tiempo a pelearme con OCaml habiendo menos recursos formativos y soporte comunitario. Sesgo, seguramente. Pero tampoco puedo dedicar tiempo a todo lo que me gustaría hacer. Para mí OCaml ha sido de los damnificados porque, en mi ignorancia, F# es OCaml en el CLR. Con el añadido de que la gestión de dependencias de F# es un orden de magnitud mejor que la ofrecida por OCaml.
Rust ha sido el otro gran candidato. Principalmente porque algunos de esos tantos proyectos que tengo en mente serían juegos. Y me atraía la idea de usar un lenguaje que compila a nativo directamente. (Kotlin tiene compilación a nativo, por cierto). Pero finalmente lo he dejado aparcado. Es el primero en la línea de sucesión. O F# o Rust, así que ya veremos…
Si tienes curiosidad, el segundo en la línea de sucesión sería Kotlin. Sí, he dicho que no usaría la JVM, pero si llego a él será bajo estos dos supuestos:
- Comodidad. Porque no tengo mucho tiempo y, al final, Kotlin —y Java— los conozco del día a día y no tendría que invertir mucho tiempo en arrancar porque eso ya lo hago a diario. Para mí Maven y Gradle son las soluciones de building que tengo casi grabadas a fuego en mi cerebro. No me cuesta pensar en términos de fases, metas, módulos, plugins, perfiles y artefactos. Algo que no me pasa con la organización de proyectos en F# ni en Rust, donde he de invertir aún mucho tiempo para andar con suficiente soltura.
- Lo que quiero/decida hacer está —mucho mejor— resuelto en el mundo Java que en .NET y/o Rust. Por ejemplo, un plugin para SonarQube no parece tener mucho sentido programarlo/escribirlo en código ajeno a la JVM. (Sí, tengo en mente un plugin —en realidad más de uno— para SonarQube).
Al final ya veremos porque en realidad, haga lo que haga, y dependiendo de lo que haga, si no es un juego, estará pensado para ejecutarse en Kubernetes y aquí la huella que dejan soluciones basadas en máquina virtual (JVM, CLR, Node…) se nota. En algunas más que en otras, pero salvo que quieras gastarte una millonada en servicios que te facturan al segundo, y a precios que no son precisamente baratos, la diferencia de impacto de ejecutar los servicios en nativo o en MV es apreciable. Y no solamente en el tamaño de las imágenes de contenedores que tiene que descargar. Los recolectores de basura son una fuente de inconsistencia impredecible bajo grandes demandas. Muchos de los últimos sustos provocados por un OOMKill de Kubernetes estaban relacionados con la gestión de memoria de Java. Aunque dudo que ese llegue a ser nunca un problema real en mi caso 😅.
¡Ah! Si alguien echa de menos lenguajes como Go decir que fue una relación de odio-odio desde el primer día. Estuve hace tres años manteniendo varios servicios escritos en Go. La idea del monorepo, lo de la carpeta vendor y su gestión de dependencias externas, y la ausencia de genéricos, es algo que me superaba. Cada vez que tenía que cambiar código/refactorizar, actualizar una dependencia o acciones que significaran hacer cambios significativos, maldecía todas y cada una de las carencias del lenguaje.
Seguramente pecaré de muchos prejuicios y sesgos, pero hay varios aspectos, principalmente tres, que exijo al lenguaje/ecosistema:
- Irrenunciable: Fuertemente tipado y estático. Que el trabajo duro lo hagan el compilador y el sistema de tipos. Cuanto más, mejor. Sin llegar a ser asfixiante (caso de Haskell).
- Un buen modelo de gestión de dependencias externas.
- Genéricos, genéricos y genéricos. El uso de genéricos sería la única forma de abstracción realmente productiva de re-utilización que poseen los lenguajes de programación. Ignorarlo es volver al pasado.
Go cojeaba —o directamente se precipitaba hacia el abismo— en las tres. Para mi gusto.
Releo y me percato de que no hago más que comparar cada opción —o casi todas— con F#. Imagino que es porque, aunque sigo teniendo dudas, es el que más me ha encajado hasta ahora, pero…
No puedo hablar de F# y su facilidad para representar dominios sin recomendar estos dos vídeos/charlas de Scott Wlaschin: Domain Modeling Made Functional y F# for C# programmers. En realidad todas las charlas de Scott Wlaschin son de obligatorio visualizado. También recomiendo disfrutar de sus decenas de artículos publicados en F# for Fun and Profit. Su libro Domain Modeling Made Functional es merecedor de una, dos o tres lecturas.
Repasando lo escrito, que es mucho más de lo que tenía en mente escribir, y para finalizar, decir que ayer, antes de ponerme a escribir esto, tenía claro, clarísimo, casi cristalino, diría que con un 99% de certeza, que finalmente usaría F# para una de las ideas que tengo. Por lo menos para la que ayer ocupaba mi creatividad. Tras escribir esto, y ver mi ejemplar de Programming Rust: Fast, Safe Systems Development, 2nd Edition sobre la mesa, las dudas vuelven a apoderarse de mí… 🙄.