- Las características del lenguaje y el ecosistema de OCaml son excelentes, y resultan adecuados tanto para proyectos personales como profesionales
- Su sistema de tipos estático, tipos algebraicos, sistema de módulos, modelo de objetos y efectos definidos por el usuario integran de forma estable múltiples paradigmas y funciones avanzadas
- Cuenta con una cadena de herramientas madura, como el gestor de paquetes OPAM, el sistema de compilación Dune, el soporte de editor LSP/Merlin y la herramienta de documentación Odoc, además de un ecosistema variado de bibliotecas para web, blockchain, tooling y más
- La comunidad destaca por su accesibilidad, amabilidad y profesionalismo, lo que facilita el aprendizaje y la colaboración, y su evolución constante le da una perspectiva prometedora a futuro
Por qué elegí OCaml como lenguaje principal
- El autor ha usado diversos lenguajes de programación durante mucho tiempo y, entre ellos, eligió OCaml como su lenguaje principal
- Entre las mayores ventajas de OCaml señala su potente sistema de tipos estático y su sobresaliente soporte para programación funcional en comparación con C u otros lenguajes funcionales
- Gracias a dicho sistema de tipos, ha experimentado una notable prevención de errores y optimización del código
- De hecho, al usar OCaml en varios proyectos de desarrollo, experimentó una gran mejora en la productividad y la estabilidad
Ventajas de OCaml y uso en la práctica
- La mayor parte del código se escribe rápidamente, y el uso de composición de funciones y datos inmutables aumenta la seguridad
- En tiempos recientes, el ecosistema y las herramientas de OCaml (IDE, sistema de compilación, etc.) también han seguido evolucionando
- Gracias a diversas bibliotecas y paquetes externos, es posible un desarrollo eficiente en entornos reales
- En comparación con Python y Java, OCaml es menos conocido, pero es una opción muy sólida en productividad, seguridad y flexibilidad
Características del lenguaje
- Su origen en la investigación, combinado con su aplicación industrial, ha impulsado funciones centradas en la expresividad y la seguridad
- Funciones modernas como efectos definidos por el usuario y sesiones affine
- La verificación estática de tipos funciona tanto como red de seguridad como herramienta de diseño, y disipa malentendidos causados por experiencias deficientes con tipos
- Multiparadigma: funcional, imperativo, modular, orientado a objetos y con soporte multicore
- La sintaxis de la familia ML es concisa y consistente, y también existen sintaxis alternativas como ReasonML
- Los tipos algebraicos (producto, suma y exponenciales), junto con pattern matching y polimorfismo, ofrecen ventajas para el modelado de datos y dominios
- El sistema de módulos admite separación entre interfaz e implementación, abstracción, reutilización e incluso polimorfismo avanzado
- Inversión de dependencias: ofrece formas flexibles de inyección mediante módulos/efectos
Ecosistema y tooling
- Objetivos de compilación: nativo, bytecode, JavaScript(
Js_of_ocaml,Melange), WebAssembly - MirageOS aporta una disciplina para escribir bibliotecas de múltiples contextos
- OCaml Platform:
- OPAM: gestión de versiones, switches, índice de paquetes y soporte para CI
- Dune: compilaciones rápidas, configuración con S-expressions y distribución simplificada mediante
dune-release - LSP/Merlin: autocompletado, navegación y formateo de código en VSCode, Emacs y otros
- Odoc: soporte para referencias cruzadas, páginas manuales y doctest
- Bibliotecas abundantes: web (Dream, Ocsigen), blockchain y criptografía (HACL*), pruebas (alcotest, qcheck, etc.)
- La biblioteca estándar es pequeña, pero existen alternativas como Batteries, Base/Core y Containers
Nuevos desafíos y comunidad
- La comunidad de OCaml es pequeña, pero sigue creciendo de forma constante y muestra una orientación amigable para el usuario
- Para desarrolladores que desean enfrentarse a nuevos lenguajes o paradigmas, OCaml vale mucho la pena para aprenderlo en profundidad
- Muchos usuarios mencionan que su experiencia con OCaml les aporta una nueva perspectiva y una mayor capacidad para resolver problemas
Conclusión
- OCaml es un lenguaje de programación potente que no se limita a áreas específicas (por ejemplo, finanzas, compiladores o desarrollo de sistemas), sino que puede usarse de forma general
- La eficiencia, mantenibilidad y capacidad de prevenir problemas obtenidas en la práctica demuestran su valor en el trabajo real
- Aunque sea algo menos conocido que lenguajes o tendencias más recientes, si se priorizan la confiabilidad y la seguridad, sin duda es una opción digna de considerarse
2 comentarios
En algún momento trabajé con OCaml en la maestría, pero de verdad le falta mucho ecosistema y casi no hay referencias; sobre todo, no hay a quién preguntarle. Según mis criterios personales, en nuestro país prácticamente no se usa fuera del ámbito de las conferencias de lenguajes de programación. De COBOL algo habrán oído, pero de OCaml probablemente no...
Opinión de Hacker News
Vi una charla sobre la experiencia de introducir Rust en el equipo de Android en Google. Dos cosas me llamaron la atención: como migraron varios proyectos de Python a Rust, parece que el rendimiento no era un problema tan grande, y las funciones que más les gustaban a los usuarios de Rust eran cosas básicas como pattern matching y los ADT (Algebraic Data Types). Por eso sentí que la contribución realmente grande de Rust no eran tanto sus características únicas, como los lifetimes, sino elementos que los lenguajes ML de los 90 ya ofrecían. Si OCaml hubiera resuelto inconvenientes como multicore alrededor de 2010, creo que podría haber sido tan popular como Rust. Lamentablemente, OCaml quedó atrapado en la brecha entre la academia y la industria. Un apunte adicional: los enteros de 31 bits son incómodos en la práctica para operaciones de bits, y estéticamente nunca me gustaron nada los dobles punto y coma
Creo que OCaml ya estaba en bastante buen estado en esa época. En 2010 lo usaba profesionalmente y era mucho más agradable que Python. Basta con ver lo que logró JaneStreet. Creo que la razón principal por la que OCaml no fue adoptado masivamente es que no fue creado ni impulsado desde EE. UU. Nos gustaría creer que la popularidad de un lenguaje se debe a su superioridad técnica, pero al final es una cuestión de moda. El éxito masivo de Rust también se debió a una gran cantidad de promoción y a una comunidad muy activa. Incluso tuvieron personal dedicado a darlo a conocer
Google intenta mantener lo más corta posible la lista de lenguajes oficiales que pueden usarse en código de producción real. Parece que eligieron Rust porque es un lenguaje que puede reemplazar o complementar a C++. OCaml difícilmente podía ocupar ese lugar (quizá podría haber reemplazado a Go, pero era poco probable). Así que la razón principal por la que eligieron Rust probablemente fue que era el único lenguaje oficial que ofrecía ADT, no que no les importara la velocidad de compilación. También era natural que OCaml no pudiera reemplazar a Rust. Ya había varios lenguajes con GC, como Go o Haskell, y alrededor de 2010 el único lenguaje con suficiente expresividad para apuntar a bare metal era C++ (y aun así, antes de C++11 y C++17 era todavía peor)
Totalmente de acuerdo. Si OCaml hubiera resuelto algunos problemas menores, de verdad podría haber sido un actor importante. La velocidad de compilación sigue siendo mucho mejor que la de Rust incluso hoy. Pero OPAM (el gestor de paquetes) suele tener bugs y es famoso por ser confuso. El soporte de Windows es gravemente malo, peor incluso que el de Perl en Windows en el pasado. La documentación oficial es tan breve que llega a ser inútil. La sintaxis misma también es difícil de captar, y es frecuente ver mensajes del tipo “la mitad del archivo tiene errores de sintaxis” por un simple typo. La sintaxis estilo C que ya traía Rust es mucho más fácil. En resumen, la ventaja de OCaml es compilar rápido, pero eso por sí solo no alcanza para justificar usarlo
Por eso, cuando quiero programar con estilo ML, antes busco Kotlin, Scala o F# que Rust. Y hoy en día hasta Java o C# ya han adoptado suficientes elementos de ML como para no causarme mucho rechazo. Estoy acostumbrado al sistema de tipos de ML desde los tiempos de Caml Light y Objective Caml, y cuando veo el entusiasmo actual por Rust, siento que a veces la gente actúa como si Rust hubiera traído el sistema de tipos de ML por primera vez
Sobre la opinión de que ojalá OCaml hubiera llegado mejor preparado, en realidad creo que la gran ventaja es que existan opciones diversas al elegir un lenguaje. Tan solo en el Reino Unido, aunque tenga menos población, conviven muchísimos idiomas. Por ejemplo, el córnico, una lengua muerta de Europa, fue revivido recientemente por la gente local, y entre pastores todavía queda Kubric, un idioma para contar números. Yo mismo empecé a usar Geneweb, un programa basado en OCAML, para el árbol genealógico de la siguiente generación (migrando desde una app de Windows llamada TMG). Tiene datos de 140 mil personas de mi familia. Como Geneweb está hecho en OCAML, eso despertó más mi interés por el lenguaje. Si te parece que los lenguajes de programación son difíciles, te recomiendo probar con genealogía: pronto te dolerá la cabeza por culpa del estándar GEDCOM
OCaml es uno de mis lenguajes favoritos. El trabajo más grande que hice fue implementar al 100% una app CRUD para la organización de un Writer's Festival usando OCaml (JSX sobre ReasonML), Dream, HTMX y DataTables. Reutilicé plantillas de frontend con módulos, y cuando cambiaba algo en el modelo de datos, el compilador me mostraba de inmediato dónde se rompía todo, lo cual me encantó. También migré datos de Excel a una base de datos real y logré hacer cosas sorprendentes dentro del ecosistema de OCaml, como exportar horarios en plantillas .odt o generar archivos zip directamente sin pasar por el disco del servidor. Aun así, fue agotador tener que escribir todas las consultas de BD como strings y hacer conversiones de tipos a mano (sin type checking en compile time). También tuve que implementar por mi cuenta el sistema de autenticación, así que terminaba dedicando demasiado tiempo a cosas que no eran el producto principal. Después de recorrer varios lenguajes, mi conclusión es que no existe el lenguaje perfecto. Todos tienen sus propios defectos únicos. Ahora estoy haciendo una app para mí mismo con Rails, y como casi todo lo que necesito ya viene por defecto, me siento mucho más satisfecho porque puedo concentrarme en el trabajo real, como el diseño del layout o el despliegue, en vez de pelearme con el lenguaje
DarkLang al principio se desarrolló en OCaml, pero luego cambió a F#. La razón principal fueron el ecosistema de librerías y la concurrencia (post relacionado). Puede que yo tenga cierto sesgo porque estoy acostumbrado a .NET, pero en las partes aburridas hay muchas opciones bien resueltas, así que uno puede concentrarse en los problemas esenciales. He usado F# profesionalmente durante bastante tiempo y también mantengo una librería popular de UI, pero como el ecosistema del lenguaje es pequeño, incluso en .NET no siempre existe una solución inmediata. Por eso, si eliges un lenguaje fuera del mainstream (por ejemplo F# en vez de C#), hay un costo que debes tener presente. Con OCaml pasa algo parecido: ofrece un lenguaje potente, pero al estar fuera del mainstream trae varias incomodidades. Algunas empresas lo usan en producción real, pero suelen ser casos adaptados a necesidades muy particulares
Traté durante años de que me gustara OCaml, pero lo que más me incomodó fue “no poder imprimir cualquier objeto arbitrario”. Puedes derivar automáticamente funciones
to_stringcon ppx, pero la configuración es molesta y la usabilidad queda por debajo de Rust. Para imprimir tipos comoSetoMaphace falta trabajo adicional (ejemplo de referencia). En golang puedes imprimir casi cualquier cosa fácilmente con el formateo"%v", pero en OCaml ese tipo de tareas requiere más esfuerzo%vde Go tampoco es perfecto, y si quieres recorrer punteros más a fondo necesitas una librería aparte como go-spew. El enfoque de__repr__de Python sigue pareciéndome el más cómodo de todos los que he vistoNo he usado OCaml directamente, pero trabajar con F# me resultó muy agradable. En esta era de los LLM, creo que valdría la pena volver a mirar los lenguajes funcionales. En paradigmas funcionales como OCaml o Haskell, la información puede comprimirse de forma eficiente en poco texto, así que quizá podría caber más significado dentro de la ventana de contexto de un LLM. Tal vez valga la pena experimentar si se pueden aplicar cambios más complejos de una sola vez que en Java, C# o Ruby
Yo también pensaba eso al principio, pero cambié de opinión al trabajar en una codebase grande de Haskell. No sé si sea porque hay poco FP en los datasets de entrenamiento, pero me parece que los lenguajes más concisos se adaptan peor a los LLM. Siento que cuando el código es más verbose, el LLM tiene más oportunidades de corregirse después de predecir tokens erróneos, y así termina generando código más correcto
En mis pruebas personales hice un juego CLI sencillo en C++ y en Haskell: Haskell tenía menos líneas, pero casi la misma cantidad de palabras, así que más bien “se veía más ancho”. No lo comparé con Java u otros lenguajes más explícitos, pero creo que el estilo adecuado depende del tipo de programa. Algunas cosas pueden encajar mejor con el estilo imperativo y otras con el funcional
Si la capacidad de los LLM para generar código mejora un poco más, me gustaría mucho poder restringir el rango de comportamiento del código con sistemas de tipos muy potentes y sistemas de efectos. Por ejemplo, con tipos dependientes se podrían verificar en compile time condiciones como “esta función siempre devuelve una lista ordenada” o “esta función siempre devuelve una solución válida de sudoku”. Si además le sumas un sistema de efectos, podrías especificar “esta función devuelve una solución válida de sudoku, pero no accede ni a la red ni al sistema de archivos”. Si los LLM siguen avanzando, quizá algún día logren algo así incluso en Python, pero si el progreso se estanca, creo que el futuro estará en envolver LLM poco confiables con sistemas deterministas y confiables
Cuando uso cats-effect (la librería de efectos) en Scala, la ayuda de los LLM me ha acelerado muchísimo el desarrollo. El código con cats-effect muchas veces hace que hasta conceptos simples se sientan difíciles, pero si solo le preguntas al LLM “¿cómo hago tal cosa en cats-effect?”, el 80% de las veces te lo resuelve de inmediato. El 20% restante sale dando contexto adicional. Desde la perspectiva del mantenimiento todavía lo estoy probando, pero la frustración de la programación funcional basada en efectos ha bajado muchísimo. La próxima vez quiero experimentar qué tan bien se desempeña Claude Code
Haskell tiene dos grandes ventajas para la generación de código con LLM. Primero, su expresivo sistema de tipos atrapa muchos errores, así que esos errores de compilación pueden volver a dársele como feedback al LLM. Segundo, es fácil mejorar código de forma eficiente y precisa usando property-based testing (QuickCheck, etc.). Los LLM no escriben tan bien los tests por sí solos, pero si se los agregas manualmente, ayudan a detectar rápido los bugs del código generado
Este artículo me hizo cerrar definitivamente la discusión de “¿por qué no usar F# en vez de OCaml?”. En casi todos los hilos sobre OCaml aparece la propuesta de “¿y si usas F# y así resuelves los problemas de tooling?”. A mí también me daba curiosidad OCaml y me llamó la atención el apodo “Go with types”, pero OCaml en sí todavía no me resulta del todo atractivo. Se siente distinto al entusiasmo de comunidades como Erlang, Ruby, Rust o Zig
Yo, en cambio, me pasé a OCaml precisamente para evitar el ecosistema de herramientas de F#. Cuando usé F#, el compilador era lento, el ecosistema giraba demasiado en torno a C#, MSBuild era débil y con mala documentación, Ionide se caía seguido, y Fantomas no era confiable, así que había muchos problemas de tooling. Aun así, OCaml tampoco reemplaza del todo las características orientadas al rendimiento que tiene F# (por ejemplo value types y otras cosas que soporta el CLR). En ese sentido, todavía no he encontrado un lenguaje simple de la familia ML que me convenza por completo. Espero que en el futuro OxCaml o algo parecido lo resuelva
Últimamente ya no uso mucho OCaml, pero el núcleo del lenguaje sigue siendo mi favorito. Mi estilo de código tiende a concentrarse en una sola función enorme, y en OCaml naturalmente evito eso. Uso Rust en side projects, pero en realidad OCaml me resulta más cómodo. Por esas razones, también me gustaría probar F# alguna vez
Tengo una pregunta sobre terminología: en el artículo llaman a los tipos funcionales “tipos exponenciales”, pero no entiendo bien por qué a los tipos de funciones de orden superior se les llama así
Ya hubo buenas explicaciones, pero la razón más profunda es que los tipos funcionales siguen algebraicamente las leyes de los exponentes. Por ejemplo,
A → (B → C)es isomorfo a(A × B) → Cmediante currying. Eso se parece a(cᵇ)ᵃ = cᵇ˙ᵃ. Y((A + B) → C)es isomorfo a(A → C) × (B → C), lo cual corresponde a la reglacᵃ⁺ᵇ = cᵃ·cᵇIncluso los tipos funcionales de primer orden ya son exponenciales. Por ejemplo, un sum type tiene tantos valores como la cantidad de casos. (Ej.:
A of bool | B of bool→ 2+2=4 casos). Lo mismo vale para los product types y los exponential types. Si lo expresas comobool -> bool, hay2^2 = 4valores posibles (ignorando efectos secundarios)Normalmente, cuando se habla de ADT (Algebraic Data Types), solo se cubren sum y product. Las funciones no suelen mencionarse porque no son datos. Pero un tipo
a -> btieneb^acasos posibles, así que puede abordarse de la misma maneraYo tenía la misma duda, pero matemáticamente después de suma y producto viene exponente, así que supongo que se llama así por analogía
Todas las respuestas anteriores son correctas, pero en realidad en teoría de categorías a los tipos funcionales se les llama “exponential product”. Ese nombre también viene de que la cantidad de funciones de A a B se calcula como cardinalidad de B elevada a la cardinalidad de A
Los casos de un sum type son expresiones con constructor de tipos (
type constructor), así que por supuesto tienen tipo. Por ejemplo,A cada caso se le asigna un tipo. Gracias al pattern matching, los parámetros del constructor de tipos ya se desempaquetan directamente. Si extraes cada caso como un tipo separado, se pierde la ventaja de exhaustiveness del sum type y terminas pudiendo representar estados inválidos del programa. Un sum type se declara una vez y se reutiliza muchas veces, y por lo general es disposable. La legibilidad del código también importa, y a veces se subestima cuánto pesa la verbosidad. Por cierto, C#/Java no soportan sum types reales. En el ejemplo de abajo se ve cómo C# se complica innecesariamente por su enfoque OOP
En ML se resuelve de forma mucho más concisa
Ambas formas son casi equivalentes, pero los elementos OOP de C# terminan siendo un estorbo
En OCaml también puedes usar GADT, variantes polimórficas (
Polymorphic Variants), etc., para usarlos como si cada uno fuera un tipo separado. Pero en general separar un sum type hace más difícil tanto generalizar como entender el código. Además, aparecen problemas de type equality y varianceNo entiendo por qué se discute tanto sum types vs sealed types. Prefiero los lenguajes funcionales, pero gracias a la diferenciación a nivel de tipos, con sealed types también se puede modelar todo lo que hacen los sum types, y por subtyping incluso hay aspectos donde definirlos y usarlos es más fácil. Los paradigmas del sistema son muy distintos, pero matemáticamente son casi equivalentes, y tanto en OOP como en FP se pueden implementar casi todos los trucos de tipos dentro de lo que permita el lenguaje
No estoy de acuerdo con que la verbosidad de declarar sum types en Java/Kotlin sea un precio aceptable. Me parece más bien el boilerplate típico de los lenguajes de la JVM
Me gustaría que alguien que conozca bien la sintaxis de ReasonML comparara sus pros y contras con ese nivel de detalle. (En el artículo apenas se menciona de pasada)
Lo que más eché de menos fue
let binding(documentación oficial). En ReasonML era fácil personalizar operadores como>>=para mónadas. rescript (el fork de ReasonML) todavía no lo tiene. En cambio, sí soporta bien la sintaxis de async/await, y eso ayuda para código asíncrono. Melange (mencionado brevemente en el artículo) sí soportalet bindingen la sintaxis Reason. Por eso, para frontend basado en React, el Reason ML de Melange tiene bastante ventaja. Gracias alet bindingy JSX, también puedes escribir código asíncrono en estilo monádico de forma limpia. En la sintaxis de OCaml se puede rodear el problema con PPX, pero en el editor el resaltado no funciona muy bien. Viéndolo desde backend, a mí me gusta el estilo de Python, así que las llaves todavía me molestan, y prefiero llamadas y definiciones de funciones sin paréntesis. Pero como usuario principiante de OCaml, sigo sintiendo que los paréntesis en argumentos posicionales y no variables son confusos. Espero que esta experiencia sirva de algoNo usé mucho ReasonML, así que no le vi las ventajas. Más allá de que “murió” dos veces en cuatro años...
Ojalá la sintaxis de Reason se hubiera difundido más, pero si quieres comunicarte con la comunidad de OCaml, conviene simplemente aprender la sintaxis estándar. La mayoría del código y la documentación usan la sintaxis estándar, así que al final igual necesitas conocerla
Lo más incómodo de ReasonML, en mi experiencia, era que el LSP no funcionaba bien
Me gustaría que explicaran con más detalle la parte de implementar dependency injection con un effects system. La idea de enlazar valores de test o de producción mediante pattern matching suena interesante, pero solo con el texto no termino de visualizarla. Y también me sorprendió enterarme de que el module system tiene su propio sistema de tipos