- Les caractéristiques du langage OCaml et son écosystème sont excellents, et conviennent aussi bien aux projets personnels qu’aux projets professionnels
- Des fonctionnalités avancées et un paradigme multiple y sont intégrés de manière fiable, notamment un système de types statique, les types algébriques, un système de modules, un modèle objet et les effets définis par l’utilisateur
- Une toolchain mature est disponible, avec le gestionnaire de paquets OPAM, le système de build Dune, le support éditeur LSP/Merlin et l’outil de documentation Odoc, ainsi qu’un écosystème riche en bibliothèques pour le web, la blockchain, le tooling, etc.
- La communauté se distingue par son accessibilité, sa bienveillance et son professionnalisme, ce qui facilite l’apprentissage et la collaboration, tandis que son évolution continue laisse entrevoir un bel avenir
Pourquoi j’ai choisi OCaml comme langage principal
- L’auteur utilise divers langages de programmation depuis longtemps, et a choisi OCaml comme langage principal parmi eux
- Parmi les plus grands atouts d’OCaml, il cite un système de types statique puissant et un excellent support de la programmation fonctionnelle, supérieur à celui du C ou de nombreux autres langages fonctionnels
- Grâce à ce système de types, il a souvent constaté une meilleure prévention des bugs et une optimisation du code
- Dans plusieurs projets de développement concrets, l’usage d’OCaml a réellement permis d’améliorer fortement la productivité et la stabilité
Les atouts d’OCaml et son usage en pratique
- La plupart du code s’écrit rapidement, et l’usage de la composition de fonctions et des données immuables améliore la sécurité
- Ces derniers temps, l’écosystème et les outils d’OCaml (IDE, système de build, etc.) continuent eux aussi de progresser
- Grâce à une grande variété de bibliothèques et de packages externes, un développement efficace est possible en environnement professionnel
- Comparé à Python ou Java, OCaml est moins connu, mais reste un choix très solide en matière de productivité, de sécurité et de souplesse
Caractéristiques du langage
- Ses origines dans la recherche et son adoption industrielle se combinent pour faire évoluer ses fonctionnalités autour de l’expressivité et de la sécurité
- Fonctionnalités récentes comme les effets définis par l’utilisateur et les sessions affines
- La vérification statique des types sert à la fois de filet de sécurité et d’outil de conception, dissipant les malentendus liés à de mauvaises expériences avec les types
- Multiparadigme : fonctionnel, impératif, modulaire, orienté objet, avec support du multicœur
- La syntaxe de la famille ML est concise et cohérente, et il existe aussi des syntaxes alternatives comme ReasonML
- Les types algébriques (produit, somme et exponentiel), le pattern matching et le polymorphisme sont particulièrement puissants pour modéliser les données et les domaines
- Le système de modules prend en charge la séparation interface/implémentation, l’abstraction, la réutilisation et même le polymorphisme avancé
- Inversion de dépendance : des mécanismes d’injection souples sont proposés via les modules et les effets
Écosystème et tooling
- Cibles de compilation : natif, bytecode, JavaScript(
Js_of_ocaml,Melange), WebAssembly - MirageOS apporte une discipline d’écriture de bibliothèques multi-contexte
- OCaml Platform :
- OPAM : gestion des versions, switchs, index de paquets, support CI
- Dune : builds rapides, configuration en S-expressions, publication simplifiée via
dune-release - LSP/Merlin : autocomplétion, navigation dans le code et formatage dans VSCode, Emacs, etc.
- Odoc : prise en charge des références croisées, pages de manuel, doctests, etc.
- Bibliothèques riches : web (Dream, Ocsigen), blockchain et cryptographie (HACL*), tests (alcotest, qcheck, etc.)
- La bibliothèque standard est petite, mais il existe des alternatives comme Batteries, Base/Core et Containers
Nouveaux défis et communauté
- La communauté OCaml est petite mais continue de croître, et suit une dynamique accueillante pour les utilisateurs
- Pour les développeurs qui veulent relever le défi d’un nouveau langage ou paradigme, OCaml mérite d’être appris en profondeur
- De nombreux utilisateurs affirment que leur expérience avec OCaml leur a apporté un nouveau regard et de meilleures capacités de résolution de problèmes
Conclusion
- OCaml est un langage de programmation puissant qui ne se limite pas à certains domaines spécifiques (comme la finance, les compilateurs ou le développement système) et peut être utilisé de manière généraliste
- L’efficacité, la maintenabilité et la capacité à prévenir les problèmes constatées en pratique prouvent sa valeur en situation réelle
- Même s’il est un peu moins connu que des langages plus récents ou plus tendance, il reste une option à envisager sérieusement si l’on privilégie la fiabilité et la sécurité
2 commentaires
J’ai déjà travaillé avec OCaml à l’époque en master, mais l’écosystème est vraiment pauvre, il n’y a pas beaucoup de références et surtout personne à qui poser des questions. Selon mes critères personnels, en Corée, en dehors du milieu des sociétés savantes sur les langages de programmation, c’est quasiment inutilisé. On a peut-être entendu parler de trucs comme COBOL, mais probablement pas d’OCaml…
Avis Hacker News
J’ai déjà vu une présentation sur l’expérience de Google avec l’introduction de Rust dans l’équipe Android. Deux points m’avaient marqué : comme divers projets avaient été migrés de Python vers Rust, les performances ne devaient sans doute pas être un enjeu si majeur ; et les fonctionnalités préférées des utilisateurs de Rust étaient surtout des choses fondamentales comme le pattern matching et les ADT (Algebraic Data Types). J’en ai donc conclu que la vraie contribution majeure de Rust ne venait pas tant de ses spécificités comme les lifetimes, mais plutôt d’éléments que les langages ML proposaient déjà dans les années 1990. Si OCaml avait résolu vers 2010 certains points pénibles comme le multicœur, il aurait probablement pu devenir aussi populaire que Rust. Malheureusement, OCaml est resté coincé dans l’écart entre le monde académique et l’industrie. Un ajout au passage : les entiers sur 31 bits sont pénibles en pratique pour les opérations bit à bit, et esthétiquement, je détestais vraiment le double point-virgule
Je pense qu’OCaml était déjà dans un état tout à fait correct à l’époque. En 2010, je l’utilisais professionnellement avec bien plus de plaisir que Python. Il suffit de voir ce qu’a accompli JaneStreet. À mon avis, la principale raison pour laquelle OCaml n’a pas été adopté plus largement, c’est qu’il n’a pas été créé ni porté par les États-Unis. On aimerait croire que la popularité d’un langage vient de sa supériorité technique, mais au fond c’est surtout une question de mode. Si Rust a réussi à percer auprès du grand public, c’est aussi grâce à un énorme effort de communication et à une communauté très active. Il y avait même du personnel dédié pour promouvoir le langage
Google essaie de garder aussi courte que possible la liste des langages officiellement autorisés pour le code de production. Rust a sans doute été choisi parce qu’il pouvait remplacer ou compléter C++. OCaml avait du mal à occuper une telle position (il aurait peut-être pu remplacer Go, mais c’était peu probable). Donc si Rust a été choisi, c’est probablement avant tout parce que c’était le seul langage officiel à fournir des ADT, et non parce que la vitesse de compilation n’était pas importante. Il est donc logique qu’OCaml n’ait pas remplacé Rust. Les langages avec GC existaient déjà en nombre, comme Go ou Haskell, et vers 2010, le seul langage suffisamment expressif pour viser le bare metal, c’était C++ (et encore, avant C++11 et C++17, c’était pire)
Tout à fait d’accord. Si OCaml avait réglé quelques problèmes mineurs, il aurait vraiment pu devenir un acteur important. La compilation reste encore aujourd’hui bien plus rapide que celle de Rust. En revanche, OPAM (le gestionnaire de paquets) a souvent des bugs et a la réputation d’être déroutant. Le support de Windows est catastrophiquement mauvais. C’est pire que le support Windows de Perl à l’époque. La documentation officielle est tellement concise qu’elle en devient presque inutile. La syntaxe elle-même est difficile à appréhender, et une petite faute de frappe produit souvent des messages du style « la moitié du fichier contient des erreurs de syntaxe ». La syntaxe de type C déjà familière de Rust est bien plus facile d’accès. En résumé, le principal avantage d’OCaml est sa compilation rapide, mais ce n’est pas une raison suffisante pour l’utiliser
Voilà pourquoi, quand j’ai envie de programmer dans un style ML, je regarde d’abord Kotlin, Scala ou F# avant Rust. Et aujourd’hui, même Java ou C# ont déjà intégré suffisamment d’éléments issus de ML pour que ça ne me rebute plus vraiment. Je suis familier du système de types ML depuis l’époque de Caml Light et Objective Caml, et quand je vois l’enthousiasme autour de Rust, j’ai l’impression que certains croient à tort que Rust a inventé ce système de types
Concernant l’idée qu’OCaml aurait dû être mieux préparé, je pense en réalité que la vraie force, c’est d’avoir un large éventail d’options pour choisir son langage. Rien qu’au Royaume-Uni, malgré une population plus réduite, il existe une immense diversité de langues. Par exemple, le cornique, langue européenne morte, a récemment été revitalisé par les habitants de la région, et chez les bergers subsiste aussi le cubit, une langue servant à compter. Moi-même, j’ai commencé à utiliser Geneweb, un programme basé sur OCAML, pour l’arbre généalogique de la prochaine génération de ma famille (après avoir quitté une appli Windows appelée TMG). Il contient les données de 140 000 personnes. Le fait que Geneweb soit écrit en OCAML a renforcé mon intérêt pour ce langage. Si vous trouvez les langages de programmation difficiles, je vous recommande d’essayer la généalogie : GEDCOM vous fera rapidement mal à la tête
OCaml fait partie de mes langages préférés. Le projet le plus important que j’ai réalisé a été une application CRUD pour l’organisation d’un Writer's Festival, implémentée à 100 % avec OCaml (JSX basé sur ReasonML), Dream, HTMX et DataTables. J’ai réutilisé des templates frontend via des modules, et dès qu’un changement touchait le modèle de données, le compilateur m’indiquait immédiatement où ça cassait, ce que j’ai trouvé extrêmement satisfaisant. J’ai aussi pu déplacer des données Excel vers une vraie base de données, générer des emplois du temps à partir de modèles au format .odt ou encore exporter directement en fichiers zip sans passer par le disque du serveur ; j’ai été surpris de voir tout ce qu’on pouvait faire dans l’écosystème OCaml. En revanche, devoir écrire toutes les requêtes de base de données sous forme de chaînes de caractères et gérer les conversions de types à la main était épuisant à un niveau absurde (pas de vérification de type à la compilation). Il fallait aussi implémenter soi-même tout le système d’authentification, ce qui faisait souvent perdre énormément de temps sur des sujets qui n’étaient pas le cœur du produit. Après avoir regardé plusieurs langages, j’en ai conclu qu’aucun langage n’est parfait. Chacun a ses défauts bien à lui. En ce moment, je développe une appli personnelle en Rails, et comme presque tout ce dont j’ai besoin est fourni par défaut, je suis bien plus satisfait : je peux me concentrer sur le vrai travail, comme le design du layout ou le déploiement réel, plutôt que sur le langage lui-même
DarkLang a d’abord été développé en OCaml, puis a ensuite migré vers F#. Les principales raisons étaient l’écosystème de bibliothèques et la concurrence (article lié). Je suis peut-être un peu biaisé parce que je connais bien .NET, mais il y a beaucoup plus d’options pour les aspects ennuyeux, ce qui permet de se concentrer sur les vrais problèmes. J’ai pas mal d’expérience professionnelle avec F#, et je maintiens aussi une bibliothèque UI populaire, mais comme l’écosystème du langage reste petit, même dans le monde .NET les solutions ne tombent pas toujours toutes seules. Il faut donc garder en tête qu’en choisissant un langage hors courant principal (par exemple F# au lieu de C#), il y a un coût. C’est pareil avec OCaml : le langage est puissant, mais comme il reste en dehors du mainstream, cela entraîne divers désagréments. Quelques entreprises l’utilisent en production, mais ce sont des cas adaptés à leurs besoins très spécifiques
J’ai essayé pendant plusieurs années d’aimer OCaml, mais le point le plus pénible pour moi était l’impossibilité de « print n’importe quel objet ». On peut générer automatiquement des fonctions
to_stringavec ppx, mais la configuration est fastidieuse et l’ergonomie reste inférieure à celle de Rust. Pour afficher des types commeSetouMap, il faut encore du travail supplémentaire (exemple de référence). En golang, on peut afficher presque tout facilement avec le formatage%v, alors qu’en OCaml cela demande plus d’efforts%vde Go n’est pas parfait non plus, et pour parcourir plus profondément des pointeurs, il faut souvent une bibliothèque séparée comme go-spew. Le modèle__repr__de Python reste, à mon avis, l’un des plus pratiques que j’aie vusJe n’ai jamais utilisé OCaml directement, mais travailler avec F# a été très agréable. À l’ère des LLM, je me dis qu’il vaudrait peut-être la peine de redonner de l’attention aux langages fonctionnels. Dans des paradigmes comme OCaml ou Haskell, on peut condenser beaucoup d’information dans peu de texte, donc peut-être faire tenir plus de sens dans la fenêtre de contexte d’un LLM. Ce serait intéressant de tester si on peut aussi y appliquer en une seule fois des changements plus complexes que dans Java, C# ou Ruby
Moi aussi je pensais ça au début, mais j’ai changé d’avis après avoir travaillé sur une grosse codebase Haskell. Peut-être parce qu’il y a peu de FP dans les données d’entraînement, mais j’ai l’impression que les langages plus concis conviennent moins bien aux LLM. Quand le code est plus verbeux, le LLM a plus d’occasions de se corriger après avoir prédit un mauvais token, ce qui l’aide à produire du code plus juste
Dans mes expériences personnelles, j’ai créé un petit jeu en CLI en C++ et en Haskell : Haskell avait moins de lignes, mais presque autant de mots, donc le code donnait surtout une impression d’être « plus large ». Je n’ai pas comparé avec Java ou d’autres langages plus explicites, mais je pense que le style adapté dépend du type de programme. Certaines choses se prêtent mieux à l’impératif, d’autres au fonctionnel
Si la génération de code par les LLM progresse encore un peu, j’aimerais vraiment pouvoir utiliser un système de types très puissant, couplé à un système d’effets, pour restreindre le comportement du code. Par exemple, avec des types dépendants (dependent types), on pourrait vérifier à la compilation des conditions comme « cette fonction renvoie toujours une liste triée » ou « cette fonction renvoie toujours une solution de sudoku valide ». En ajoutant un système d’effets, on pourrait en plus spécifier « cette fonction renvoie une solution de sudoku valide, mais n’accède ni au réseau ni au système de fichiers ». Si les LLM continuent à progresser, ils finiront peut-être par faire ce genre de chose même en Python ; mais si leur progression ralentit, je pense que l’avenir consiste à encapsuler des LLM peu fiables dans des systèmes déterministes et fiables
En utilisant cats-effect (la bibliothèque d’effets) en Scala, l’aide des LLM a énormément accéléré mon développement. Le code cats-effect rend souvent difficiles même des concepts simples, mais il suffit de demander au LLM « comment faire ~ avec cats-effect ? » et 80 % du problème est résolu immédiatement. Pour les 20 % restants, il suffit de fournir un peu plus de contexte. Du point de vue de la maintenance, je suis encore en phase d’essai, mais la frustration liée à la programmation fonctionnelle basée sur les effets a nettement diminué. La prochaine fois, j’aimerais tester jusqu’où Claude Code s’en sort bien
Haskell a deux grands avantages pour la génération de code par LLM. D’abord, son système de types très expressif attrape beaucoup d’erreurs, et on peut ensuite renvoyer au LLM les erreurs de compilation comme feedback. Ensuite, il est facile d’améliorer efficacement et précisément le code grâce aux tests basés sur les propriétés (QuickCheck, etc.). Les LLM ne sont pas forcément très bons pour écrire les tests eux-mêmes, mais si on les ajoute à la main, ils permettent de repérer rapidement les bugs du code généré
Après avoir lu cet article, j’ai enfin l’impression d’avoir une réponse définitive à la question « pourquoi ne pas utiliser F# plutôt qu’OCaml ? ». Dans presque tous les threads sur OCaml, quelqu’un finit par proposer « et si tu utilisais F#, ça ne réglerait pas les problèmes d’outillage ? ». OCaml m’intriguait aussi, surtout après avoir vu le surnom « Go with types », mais pour l’instant le langage lui-même ne m’attire pas encore complètement. L’énergie de sa communauté me semble différente de celle d’autres langages comme Erlang, Ruby, Rust ou Zig
Moi, c’est justement pour éviter l’écosystème d’outils de F# que je suis passé à OCaml. Quand je l’utilisais, F# souffrait de nombreux problèmes d’outillage : compilateur lent, écosystème centré sur C#, MSBuild faible et mal documenté, Ionide qui plantait sans cesse, Fantomas peu fiable, etc. Cela dit, OCaml ne remplace pas non plus toutes les fonctionnalités orientées performance de F# (par exemple les value types et autres éléments fournis par le CLR). En ce sens, je n’ai toujours pas trouvé de langage ML simple qui me convienne vraiment. J’espère qu’OxCaml ou d’autres projets finiront par résoudre cela
Je n’ai pas beaucoup utilisé OCaml récemment, mais le cœur du langage reste encore ce que je préfère. Mon style de code a tendance à se concentrer dans une énorme fonction, et OCaml m’aide naturellement à éviter ça. J’utilise Rust pour des side projects, mais en réalité OCaml me paraît plus confortable. Pour cette raison, j’aimerais aussi vraiment essayer F#
J’ai une question de terminologie : dans l’article, les types de fonction sont appelés « exponential types », mais je ne comprends pas bien pourquoi un type de fonction d’ordre supérieur est qualifié d’exponentiel
Il y a déjà de bonnes explications, mais la raison plus profonde, c’est que les types de fonction suivent algébriquement les lois des exposants. Par exemple,
A → (B → C)est isomorphe à(A × B) → Cvia le currying. C’est analogue à(cᵇ)ᵃ = cᵇ˙ᵃ. De même,(A + B) → Cest isomorphe à(A → C) × (B → C), ce qui correspond à la règlecᵃ⁺ᵇ = cᵃ·cᵇMême les types de fonction du premier ordre sont déjà exponentiels. Par exemple, un sum type contient autant de valeurs qu’il a de cas. (Par exemple :
A of bool | B of bool→2+2=4possibilités.) Les product types et les exponential types suivent la même logique. Pourbool -> bool, il y a2^2 = 4valeurs possibles (si l’on ignore les effets de bord)Quand on parle d’ADT (Algebraic Data Types), on s’arrête généralement aux sum types et aux product types. Les fonctions ne sont pas des données, donc on les mentionne moins souvent. Mais comme un type
a -> bab^acas possibles, on peut l’aborder exactement de la même manièreJe me posais la même question, mais mathématiquement, après la somme (sum) et le produit (product), vient l’exponentiation, donc je suppose qu’on l’appelle ainsi par analogie
Toutes les réponses précédentes sont justes, mais en réalité, en théorie des catégories (category theory), on appelle les types de fonction des « exponential product ». Le nom vient aussi du fait que le nombre de fonctions de
AversBse calcule comme le cardinal deBà la puissance du cardinal deALes cas d’un sum type sont des valeurs (expressions) produites par des constructeurs de type, donc ils ont évidemment un type. Par exemple,
chacun des cas se voit attribuer un type. Grâce au pattern matching, on déballe déjà immédiatement les paramètres du constructeur. Si on extrait chaque cas dans un type séparé, on perd l’avantage d’exhaustivité du sum type, et on finit au contraire par représenter des états de programme invalides. Un sum type se déclare une seule fois puis s’utilise plusieurs fois, et il est souvent disposable. La lisibilité du code compte aussi, et on sous-estime parfois le coût de la verbosité. À noter que C# et Java ne prennent pas réellement en charge les vrais sum types. Dans l’exemple ci-dessous, C# est inutilement compliqué à cause de son approche OOP
En ML, c’est bien plus concis
Les deux approches sont presque équivalentes, mais les éléments OOP de C# deviennent ici plutôt un obstacle
En OCaml, on peut aussi utiliser des GADT, des polymorphic variants, etc., pour s’en servir comme de types séparés. Mais en général, séparer un sum type rend la généralisation plus difficile et la compréhension moins évidente. Cela s’accompagne aussi de problèmes d’égalité de types et de variance
Je ne vois pas pourquoi on tient absolument à opposer sum types et sealed types. Je préfère les langages fonctionnels, mais grâce aux distinctions au niveau des types, les sealed types suffisent aussi à modéliser tous les sum types, et le sous-typage peut même parfois rendre la définition et l’usage plus simples. Les paradigmes des systèmes sont très différents, mais mathématiquement ils sont presque équivalents, et les tricks de types qu’on peut faire en OOP ou en FP sont pratiquement tous réalisables dans les limites autorisées par le langage
Je ne suis pas d’accord avec l’idée que la verbosité des déclarations de sum types en Java/Kotlin vaut le coup. Ça donne surtout l’impression du boilerplate typique des langages JVM
J’aimerais bien qu’une personne connaissant vraiment bien la syntaxe ReasonML compare ses avantages et ses inconvénients. (L’article ne l’évoque que brièvement)
Ce qui m’a le plus manqué, c’est le let binding (documentation officielle). Avec ReasonML, on pouvait facilement personnaliser des opérateurs comme
>>=pour les monades. rescript (le fork de ReasonML) ne l’a toujours pas. En revanche, sa syntaxeasync/awaitest bien prise en charge, ce qui aide pour le code asynchrone. Melange (brièvement mentionné dans l’article) prend en charge le let binding dans la syntaxe Reason. Pour un frontend basé sur React, le Reason ML de Melange est donc très avantageux. Grâce au let binding (et à JSX), on peut écrire proprement du code asynchrone en style monadique. Avec la syntaxe OCaml, on peut contourner ça via PPX, mais la coloration de l’éditeur ne suit pas toujours bien. Côté backend, j’aime le style Python, donc les accolades me gênent encore, et je préfère les appels et définitions de fonctions sans parenthèses. Mais en tant qu’utilisateur débutant d’OCaml, l’usage des parenthèses avec les arguments non variables reste encore déroutant pour moi. J’espère que ce retour sera utileJe n’ai pas assez utilisé ReasonML pour vraiment en percevoir les avantages. À part le fait qu’il est mort deux fois en quatre ans...
J’aimerais que la syntaxe Reason soit plus largement adoptée, mais si on veut communiquer avec la communauté OCaml, il vaut mieux apprendre directement la syntaxe standard. La plupart du code et de la documentation l’utilisent, donc il faudra de toute façon la connaître
Le point le plus pénible dans mon expérience avec ReasonML, c’était que le LSP ne fonctionnait pas correctement
J’aimerais qu’on détaille davantage la manière d’implémenter la dependency injection avec un système d’effets. L’idée de lier des valeurs de test ou de production via du pattern matching a l’air intéressante, mais je ne vois pas bien comment cela fonctionne en lisant seulement l’article. Et j’ai aussi trouvé fascinant d’apprendre que le système de modules possède son propre système de types