URL: https://linuxfr.org/news/ocaml-4-03 Title: OCaml 4.03 Authors: octachron kantien, chicco, Lucas, VictorAche, Benoît Sibaud, Ontologia, BAud, Nicolas Boulay, Yves Bourguignon, gim, Snark, Nÿco, palm123, Storm, ɹǝıʌıʃO, Nicolas Casanova, anaseto, KlakPlok, Florent Zara, Bruno Michel, come, esdeem, Dinosaure, theblatte, Anthony Jaguenaud, patrick_g et Anonyme Date: 2016年03月02日T09:36:11+01:00 License: CC By-SA Tags: ocaml, langage et sortie_version Score: 73 La version 4.03.0 du langage OCaml est paru le 25 avril 2016. OCaml est un langage fonctionnel de la famille des [langages ML](https://fr.wikipedia.org/wiki/ML_%28langage%29) (dont font partie [SML](https://fr.wikipedia.org/wiki/Standard_ML) et [F#](https://fr.wikipedia.org/wiki/F_sharp), ou [Rust](https://www.rust-lang.org/) avec une définition élargie). OCaml est entre autre utilisé pour implémenter le langage [Coccinelle](https://en.wikipedia.org/wiki/Coccinelle_%28software%29) (régulièrement utilisé dans la communauté des développeurs du noyau Linux) ou [MirageOS](https://mirage.io/) (ensemble de bibliothèques pour construire des [unikernels](https://fr.wikipedia.org/wiki/Unikernel)). On compte aussi l'implémentation du langage [Hack](https://fr.wikipedia.org/wiki/Hack_%28langage%29) chez Facebook, l'interpréteur de référence pour le projet [[WebAssembly]], ou encore l'analyseur statique de Code C [Frama-C](https://en.wikipedia.org/wiki/Frama-C). ![OCaml](https://ocaml.org/img/colour-logo-white.svg) Il s'agit d'un langage fonctionnel multi-paradigmes fortement typé qui permet de mélanger librement les [paradigmes](https://fr.wikipedia.org/wiki/Paradigme_%28programmation%29) fonctionnel, impératif et objet. Cette version 4.03 fait suite à la [version 4.02 publiée en juillet 2015](https://ocaml.org/releases/4.02.html). ---- [OCaml 4.03: Everything Else](https://blogs.janestreet.com/ocaml-4-03-everything-else/) [Présentation de flambda](https://blogs.janestreet.com/flambda/) [Liste des changements](https://github.com/ocaml/ocaml/blob/4.03/Changes) [Optimiser son code avec les flambdas](http://caml.inria.fr/pub/docs/manual-ocaml-4.03/flambda.html) [Présentation de la version 4.03 sur LWN](http://lwn.net/Articles/684128) [Manuel de référence](http://caml.inria.fr/pub/docs/manual-ocaml-4.03/) [Site officiel](http://ocaml.org/) [Annonce de publication de la version 4.03](http://alan.petitepomme.net/cwn/2016.04.26.html#2) [Dépêche LinuxFr.org sur OCaml 4.02](https://linuxfr.org/news/ocaml-4-02) ---- #En bref OCaml fête ses vingt ans, c'est l'occasion de revenir sur l'évolution de la communauté de ce langage performant, et sur ses possibles évolutions futures. Cette nouvelle version d'OCaml s'est concentrée sur les performances du code généré, à travers l'introduction d'une nouvelle phase d'optimisation Flambda ainsi qu'une amélioration de la latence du ramasse-miette lui-même. La gestion de la mémoire dans OCaml est automatique. À ce titre, le rôle du ramasse-miette est très important : que ce soit en termes de latence, vitesse ou de consommation mémoire. La bibliothèque standard s'enrichit de nouvelles possibilités avec les éphémérons, et accueille des types de compatibilité standard pour faciliter la coopération entre les bibliothèques externes. Les types algébriques, fonctionnalité importante dans un langage qui se base sur des types rigides pour l'élégance et la sécurité du code, ont été améliorés avec un mélange plus simple entre les types sommes (un choix entre sous-types) et les types enregistrés (plusieurs sous-types étiquetés par des noms, équivalent aux structures en C). Dans un registre plus avancé, la gestion des types algébriques généralisés (GADT) a été améliorée avec de nouvelles possibilités pour détecter des erreurs de logique à la compilation, une fonctionnalité centrale des langages ML, et des messages d'erreurs plus clairs. Si cette version est riche en changements, le futur du langage est aussi riche en possibilités : de nombreuses nouvelles fonctionnalités peuvent être testées dans des branches expérimentales du compilateur : multicœur, implicites modulaires, opérateurs d'indexation. De nombreuses autres améliorations mineures ont été apportées au langage et outils associés ; par exemple, une syntaxe non dépendante, plus lisible, pour les [foncteurs](https://fr.wikipedia.org/wiki/Foncteur). #Communauté ##Ocaml devient LGPL + linking exception Un des aspects moins techniques de cette nouvelle version 4.03 est le changement de licence du compilateur. Précédemment, le compilateur OCaml et les bibliothèques et outils associés étaient distribués sous la licence [Q Public License](https://fr.wikipedia.org/wiki/Q_Public_License). À partir de cette version, ils sont publiés sous licence LGPL + _linking exception_. Cette exception à la liaison s'expliquant par le fait qu'OCaml lie les bibliothèques statiquement par défaut. Publier une bibliothèque OCaml sous licence LGPL est donc de facto équivalent à la publier sous licence GPL. ##Github devient le dépôt officiel du compilateur Un autre changement important, le dépôt github [OCaml](https://github.com/ocaml/ocaml) devient le dépôt officiel du code source d'OCaml. Ce dépôt avait été introduit en janvier 2014 comme un miroir du dépôt svn officiel. Le but était alors d'expérimenter si passer à un dépôt git hébergé sur Github aiderait à attirer des contributeurs extérieurs. L'expérience fut concluante et le miroir est devenu l'original. L'ancien traqueur de bugs ([Mantis](http://caml.inria.fr/mantis/)) ne recevait qu'une douzaine de patchs par an, là où le dépôt Github a reçu 130 requêtes de tirage en 2014, 254 en 2015 et il y en a déjà 156 en 2016. Le [manuel de référence](http://caml.inria.fr/pub/docs/manual-ocaml/) d'OCaml a été aussi intégré au sein du dépôt officiel. Si le manuel est relativement complet, il est parfois assez austère et certaines informations avancées sont manquantes ou difficilement accessibles. De ce fait, les utilisateurs doivent souvent se rabattre sur des tutoriels et articles de blogs. Cette version d'OCaml a vu un effort important pour améliorer cette lisibilité du manuel, mais il s'agit d'un travail de longue haleine et contributions ou remarques sont les bienvenues. ##Ocamlbuild séparé du compilateur Simultanément, il a été décidé de séparer Ocamlbuild, un outil d'aide à la compilation de bibliothèques et d’exécutable, du compilateur. Ocamlbuild vit désormais dans son [propre dépôt](https://github.com/ocaml/ocaml). L'idée derrière cette séparation est de découpler les sorties du compilateur de celles d'Ocamlbuild afin de permettre à celui-ci d'évoluer à un rythme plus soutenu que celui du compilateur, et que ses améliorations deviennent utilisables avec des versions plus anciennes du compilateur. Cette séparation a fait l'objet d'un [long débat](http://caml.inria.fr/mantis/view.php?id=6402), certains mainteneurs d'Ocamlbuild pensant que ce changement ne modifierait pas le cycle de vie un peu trop calme d'Ocamlbuild (deux contributeurs actifs). D'aucuns ont soutenu au contraire qu'une fois libéré des contraintes de temps impliquées par la synchronisation avec le compilateur, il deviendrait bien plus facile d'attirer de nouveaux contributeurs. En particulier, s'il est nécessaire d'attendre 6 mois pour voir une correction intégrée au système de compilation, il est bien plus simple de modifier son propre projet plutôt que corriger le bug en amont. ##OCaml a fêté ses 20 ans Le 12 septembre 2015, le langage OCaml a fêté ses 20ans. \o/ [Xavier Leroy](https://github.com/xavierleroy), le [[BDFL]] du langage, a posté ce jour-là [un message sur la liste de diffusion](http://alan.petitepomme.net/cwn/2015.09.15.html#3) dans lequel il fait un rapide tour d'horizon des vingt années écoulées. Le langage s'appelait au départ Caml Special Light, avant de devenir Objective Caml puis finalement OCaml. Le design d'origine est toujours présent, mais il a acquis des fonctionnalités qui étaient encore des problèmes de recherche ouverts en 1995 comme : les objets et les classes avec inférence de types, les variants polymorphes, le polymorphisme de première classe ou encore les modules de première classe. [Une histoire plus complète du langage se trouve sur le site ocaml.org](https://ocaml.org/learn/history.html). Ce dernier s'est également doté de [règles de gouvernance](https://ocaml.org/governance.html) en septembre dernier. #Amélioration du compilateur La nouveauté majeure de cette nouvelle version est interne au compilateur, qui voit l'introduction d'un ensemble de nouvelles passes d'optimisations. Ces nouvelles passes d'optimisations reposent sur une nouvelle représentation intermédiaire : Flambda. En fonction des circonstances, le taux d'allocation (la mémoire allouée dynamiquement pendant l'exécution et gérée par le ramasse-miettes) du code généré par le compilateur natif peut diminuer de 20 à 30% sur du code réel. Il faut toutefois noter que ces améliorations massives proviennent au moins en partie du fait que le compilateur natif d'OCaml n'effectuait que peu d'optimisations de haut niveau jusqu'à présent ; il se reposait plus sur sa représentation mémoire efficace pour obtenir des performances correctes. Avec cette amélioration du compilateur, les commandes les plus communes pour compiler sont les suivantes : ``` ocamlopt -Oclassic file.ml ocamlopt file.ml ocamlopt -O2 file.ml ocamlopt -O3 file.ml ``` La première commande mime le comportement antérieur du compilateur (avec quelques optimisations de plus), la seconde fait une passe d'optimisation en exploitant les nouvelles possibilités offertes par Flambda, enfin les deux dernières font respectivement deux et trois passes d'optimisations. Chacune, sauf le mode classique, peut être complétée de l'option `unbox-closures`, dont le principe de fonctionnement est décrit plus loin. Voir [la page de manuel](http://caml.inria.fr/pub/docs/manual-ocaml-4.03/flambda.html) pour plus de détails sur les options disponibles. Pour l'instant cela augmente le temps de compilation de façon importante (même avec l'option `-Oclassic`) ce qui explique que Flambda n'est pas encore activé par défaut. À terme, l'objectif est d'amener la hausse du temps de compilation autour de 10 % pour `-Oclassic`, et entre 10 % et 20 % avec une passe : Flambda sera alors mis en place par défaut. Pour bénéficier de ces optimisations il faut utiliser une option de configuration du compilateur ou, pour les utilisateurs d'[opam](http://opam.ocaml.org/), utiliser le _switch_ 4.03.0+flambda : `opam switch 4.03.0+flambda`. Lorsque l'on cherche à optimiser du code, que ce soit manuellement en modifiant l'implémentation de l'algorithme, ou bien automatiquement en modifiant le code machine généré par le compilateur, il faut habituellement arbitrer entre des gains, ou pertes, en temps et en espace : le code est plus rapide mais il prend plus de place, il est moins rapide mais plus court (et incidemment, peut être moins sujet aux bugs car la vérification humaine est plus simple). En revanche si l'on peut gagner à la fois en temps et en espace, il n'y a pas à hésiter. Les optimisations apportées par Flambda n'échappent pas à la règle : les binaires générés peuvent être plus gros mais plus rapide (ce qui se traduit par une moins grande consommation de mémoire à l'exécution), ce qui se paye au prix d'un temps de compilation accru (plus ou moins en fonction des options d'optimisations choisies), mais au bénéfice d'une plus grande sûreté (le processus d'optimisation est automatisé dans le compilateur et donc moins sujet aux erreurs humaines) : le développeur à l'origine de Flambda expose sa vision dans son article de blog [« Les optimisations que vous ne devriez pas faire : faire le travail du compilateur »](http://www.ocamlpro.com/2013/05/24/optimisations-you-shouldnt-do/). Un des apports fondamentaux de Flambda au compilateur `ocamplopt` est une amélioration du processus d'[extension en place](https://fr.wikipedia.org/wiki/Extension_inline) (extension inline, ou *inlining*) qui consiste à remplacer un appel à une fonction par le code de la fonction elle-même. Une fois ce processus effectué d'autres optimisations deviennent possibles, nous présenterons donc son principe en premier. ##Extension en place (inlining). Comme exposé ci-dessus, l'*inlining* consiste à remplacer un terme `f x` par le code de la fonction `f` dans lequel on remplace toutes les occurrences de son paramètre par le terme `x`. Cela peut permettre, ensuite, certaines simplifications du code comme dans l'exemple ci-dessous : ```ocaml let f x = x + 1 let g y = f (f y) (* pour remplacer le code de f dans celui de g, on le fait successivement en introduisant des variables fraîches, ce qui donne *) let g y = let r1 = y + 1 in (* première application de f *) let r2 = r1 + 1 in (* deuxième application de f *) r2 (* ce qui après remplacement des nouvelles variables par leur expression et simplification donne *) let g y = y + 2 ``` Une compilation « naïve » de la fonction `g` qui consisterait à faire un double appel au code de `f` aurait eu pour exécution la version développée qui alloue deux variables temporaires et effectue deux appels de fonctions, c'est coûteux comparé à la version définitive optimisée. Bon, je rassure tout le monde : le compilateur `ocamlopt` savait déjà faire ce type de simplification. Mais il existait des cas plus évolués où sa stratégie d'*inlining* et de simplification ne permettait pas certaines optimisations comme dans le cas des fonctions récursives (fonctions pourtant utilisées à tour de bras dans un langage fonctionnel comme OCaml), ainsi que l'illustre l'exemple suivant : ```ocaml (* on prend la fonction List.map qui consiste à appliquer une fonction à tous les éléments d'une liste et renvoie la liste résultante on peut la coder ainsi *) let rec list_map f l = match l with | [] -> [] | hd :: tl -> (f hd) :: list_map f tl (* on peut ensuite coder une fonction succ_map qui renvoie la liste de tous les successeurs des éléments d'une liste *) let succ_map l = let succ = (fun x -> x + 1) in list_map succ l (* on insère le code de list_map dans celui de succ_map avant son appel *) let succ_map l = let succ = (fun x -> x + 1) in let rec list_map' f l = match l with | [] -> [] | hd :: tl -> (f hd ) :: list_map' f tl in list_map' succ l (* une fois recopié on l'applique à notre cas *) (* maintenant le paramètre f de la fonction dupliquée est constamment égal à succ et on peut pratiquer la substitution dans le corps de list_map' *) let succ_map l = let succ = (fun x -> x + 1) in let rec list_map' f l = match l with | [] -> [] | hd :: tl -> (succ hd ) :: list_map' succ tl in list_map' succ l (* là on voit que le paramètre f n'est plus utilisé donc on peut supprimer toute référence à celui-ci *) let succ_map l = let succ = (fun x -> x + 1) in let rec list_map' l = match l with | [] -> [] | hd :: tl -> (succ hd ) :: list_map' tl in list_map' l (* enfin en remplaçant la fonction succ par son expression dans la boucle, on peut supprimer toute référence à celle-ci *) let succ_map l = let rec list_map' l = match l with | [] -> [] | hd :: tl -> (hd + 1) :: list_map' tl in list_map' l ``` Le processus décrit ci-dessus consiste en une *spécialisation* de la fonction `List.map` appliquée à la fonction `succ`. Au départ à chaque tour de boucle on a un appel à la fonction `succ` (ce qui est coûteux tant en temps qu'en espace), là où cet appel a disparu dans la version définitive *spécialisée* et *optimisée*. Néanmoins, le développeur peut se contenter d'écrire `let succ_map = List.map succ` et le compilateur se charge du reste : *les optimisations que vous ne devriez pas faire : faire le travail du compilateur*. Une autre stratégie utile d'optimisation proposée par Flambda consiste dans le *unbox closure*, c'est-à-dire examiner la représentation en mémoire des fonctions, afin d'éviter des appels inutiles à des fonctions et économiser l'allocation de mémoire. ##Deconstruction des fermetures (unbox closure) Dans un langage fonctionnel, il peut arriver des choses assez étranges : que les variables utilisées par une fonction ne soient plus accessibles dans l'environnement syntaxique quand elles sont exécutées. Par exemple : ```OCaml let augmente n = let n2 = n + n in let plus_n2 x = x + n2 in plus_n2 let plus_n2' = augmente 3 let dix = plus_n2' 4 ``` Dans cet exemple `n2` n'est plus disponible au moment où on appelle `plus_n2`, il faut donc le stocker quelque part. Pour faire ça, le compilateur transforme ce code en ajoutant l'allocation d'un bloc mémoire qui permettra de stocker `n2`. Ce bloc est appelé une [fermeture (ou clôture)](https://fr.wikipedia.org/wiki/Fermeture_%28informatique%29). Comme c'est quelque chose de très courant, OCaml a une représentation très efficace des fermetures, mais cela a quand même un coût. Parfois certaines fermetures sont introduites dans des environnements où les valeurs qui y sont stockées sont disponibles par d'autres moyens. Dans ces cas-là, l'optimisation `unbox-closures` permet de se passer de l'allocation de ces fermetures en passant les valeurs qui y seraient contenues en argument. Pour les connaisseurs, cela est assez similaire à une `eta-expansion`. Cette transformation n'est activée que quand l'option `-unbox-closures` est passée au compilateur. Pour ceux que ça peut intéresser, schématiquement, la stratégie du `unbox-closures` a pour fonction de ramener les variables globales à des variables locales aux fonctions. Sur `plus_n2` en un coup, ça fait grosso-modo ```ocaml let augmente n = let plus_n2 x = let n2 = n + n in x + n2 in plus_n2 (* qui peut se simplifier en *) let augmente n = let plus_n2 x = x + n + n (* plus_n2 = fun x -> x + n + n *) in plus_n2 (* et si on passe à la fermeture ça donne *) let augmente = (fun n -> (fun x -> x + n + n)) (* mais la fermeture est vue du point de vue de plus_n2 à la manière d'un chroot, donc la fermeture contient le code fun n -> (fun x -> ... ) plus la façon d'accéder à n qui est hors de la portée syntaxique de plus_n2 *) ``` Et quand on appelle `augmente 3`, le compilateur comprend que l'on se retrouve dans une situation analogue à ```ocaml let n = 3 let plus_n2' x = x + n + n (* qu'il simplifie par unbox-closures en *) let plus_n2' = (fun n -> (fun x -> x + n + n)) 3 (* puis par substitution en *) let plus_n2' = (fun x -> x + 3 + 3) (* et enfin par calcul *) let plus_n2' = (fun x -> x + 6) (* et la suite il le voit comme *) let dix = (fun x -> x + 6) 4 (* soit *) let dix = 10 ``` Au fond, le compilateur exécute déjà une partie du code et stocke statiquement le résultat (c'est pour cela que la compilation est plus longue), au lieu de le recalculer à chaque exécution du programme. Un peu plus de détails : en plus d'ajouter des arguments — ce qui demande pas mal de manipulations de fermetures pour être capable d'exposer la fonction optimisée, mais aussi la version originale avec l'ABI classique pour les appels indirects et les cas récursifs — il y a une analyse d'alias pour les cas qui ressemblent à ```OCaml let f n l = let n2 = n + n in let stuff x = n2 + x in List.map stuff l ``` Après spécialisation: ```OCaml let f n l = let n2 = n + n in let stuff x = n2 + x in let rec list_map_spécialisé f l = (* sachant que f = stuff, c'est une annotation portée par la fermeture *) match l with | [] -> [] | h :: t -> let resultat_de_stuff = let n2 = f.acces_dans_la_fermeture_de_stuff in h + n2 in resultat_de_stuff :: list_map_spécialisé f t in list_map_spécialisé stuff l ``` Du coup `stuff` ne peut pas être nettoyé parce qu'il est utilisé par le `list_map` et sa fermeture est utilisée. D'où l'utilité d' `unbox-closures` qui va retrouver cet accès, puis voir que `stuff` est dans l'environnement au point de déclaration de `list_map_spécialisé` et donc réécrire en ```OCaml let f n l = let n2 = n + n in let stuff x = n2 + x in let n2' = f.acces_dans_la_fermeture_de_stuff in let rec list_map_spécialisé f l n2' = (* sachant que f = stuff, c'est une annotation portée par la fermeture *) match l with | [] -> [] | h :: t -> let resultat_de_stuff = h + n2' in resultat_de_stuff :: list_map_spécialisé f t n2' in list_map_spécialisé stuff l n2' ``` Puis l'analyse d'arguments morts va voir que f ne sert à rien et réécrire en ```OCaml let f n l = let n2 = n + n in let stuff x = n2 + x in let n2' = f.acces_dans_la_fermeture_de_stuff in let rec list_map_spécialisé l n2' = match l with | [] -> [] | h :: t -> let resultat_de_stuff = h + n2' in resultat_de_stuff :: list_map_spécialisé t n2' in list_map_spécialisé stuff l n2' ``` Puis la propagation d'alias va voir que `n2'` est aussi `n2` et nettoyer. ```OCaml let f n l = let n2 = n + n in let rec list_map_spécialisé l n2' = match l with | [] -> [] | h :: t -> let resultat_de_stuff = h + n2' in resultat_de_stuff :: list_map_spécialisé t n2' in list_map_spécialisé stuff l n2 ``` ##Attributs prédéfinis Depuis OCaml 4.01, il est possible d'utiliser des attributs prédéfinis avec la syntaxe `[@.. attribut]` pour préciser le comportement du compilateur, par exemple pour activer des avertissements spécifiques sur une section critique du code. Le cru 4.03 apporte son lot de nouveaux attributs prédéfinis. Avec l'accent sur l'optimisation dans cette version, il n'est guère surprenant de voir apparaître de nouveaux attributs qui permettent de contrôler les optimisations effectuées par le compilateur ou de vérifier qu'une optimisation a été bien appliquée. ###Contrôle d'optimisations Les attributs `inline`, `unroll` et `specialise` permettent d'indiquer qu'une fonction ou un [foncteur](http://caml.inria.fr/pub/docs/manual-ocaml-4.03/moduleexamples.html#sec20) devrait toujours (ou jamais) être incorporé en ligne (ou déroulée ou spécialisée pour les fonctions récursives). ```OCaml module Float = struct let (+) = (+.) [@@inline] end ``` ```OCaml let rec list_map fonction liste = match liste with | [] -> [] | tete :: queue -> fonction tete :: list_map fonction queue [@@specialise] [@@unroll 1] ``` Un autre attribut `[@immediate]` permet d'indiquer au compilateur qu'un type abstrait (dont l'implémentation a été cachée au compilateur) a une représentation immédiate qui ne contient pas de pointeurs. Les exemples principaux de types immédiats sont les types int, char, bool mais aussi les types sommes dont tous les constructeurs sont constants. ```OCaml module M: sig type t [@@immediate] end = struct type t = A | B end ``` Cet attribut permet donc de marquer le type `t` comme un type immédiat, améliorant potentiellement les performances du code généré par le compilateur, sans exposer totalement sa représentation interne. ###Vérification d'optimisations En plus de pouvoir contrôler les optimisations effectuées par le compilateur, de nouveaux attributs permettent de vérifier que des optimisations sont bien appliquées. L'attribut `[@inlined]` demande au compilateur d'incorporer le code de la fonction ou du foncteur: ```OCaml (f[@inlined]) ();; ``` Si cela n'est pas possible, le compilateur émet un avertissement. Cela peut se produire si par exemple la provenance de la fonction ne peut être déterminée statiquement à la compilation. Les attributs `unroll` et `specialise` ont aussi leur versions pour le point d'appel: `unrolled` et `specialised`: ```OCaml let rec list_map fonction liste = match liste with | [] -> [] | tete :: queue -> fonction tete :: (list_map [@unrolled 3]) fonction queue let successeur_de_liste l = (list_map [@@specialised]) ((+) 1) l ``` De manière similaire, l'attribut `[@tailcall]` demande au compilateur d'émettre un avertissement si l'appel de fonction annoté n'est pas un appel [récursif terminal](https://fr.wikipedia.org/wiki/R%C3%A9cursion_terminale) et ne peut être optimisé. ```OCaml let rec no_op l = match l with | [] -> () | _ :: q -> (no_op[@tailcall]) q;; (* ici l'appel est bien récursif terminal, et nous pouvons ne rien faire de façon optimisée *) ``` ###Optimisation de l'appel de code C L'interface entre OCaml et le C a été améliorée avec de nouveaux attributs qui permettent de spécifier si une fonction C n'effectue pas d'allocation de valeur OCaml `[@@noalloc]`, ou bien si un de ses arguments utilise un entier non marqué (`[@untagged]`) ou un flottant non encapsulé (`[@unboxed]`). ##Prise en charge de l'architecture System Z [IBM a contribué](https://github.com/ocaml/ocaml/pull/275) la prise en charge de l'architecture [[System_z]], aussi appelée S390x, au compilateur natif. Malheureusement assez peu de monde aura accès à ce genre de machine pour pouvoir tester. Cette contribution est un assez fort signal de l'usage d'OCaml dans le secteur bancaire. ##Amélioration du glaneur de cellules Le glaneur de cellules (GC ou ramasse-miettte) d'OCaml est de la catégories des GC précis, générationnel, incrémental, avec une génération copiant et l'autre traçant et compactant. Ce fouillis de mots clefs veut dire: * précis: le GC est toujours capable de distinguer un pointeur des autres types de valeurs. C'est important pour pouvoir fournir les autres propriétés * générationnel: Le tas est segmenté en plusieurs parties (pour OCaml, 2), appelées générations, qui représentent des valeurs d'âges différents. En OCaml, il y a la jeune génération (appelé tas mineur, dans le contexte de OCaml) qui est un GC copiant, et le tas majeur qui est traçant et compactant. * traçant : le GC est fait en plusieurs phases : * il parcourt d'abord toutes les valeurs vivantes et marque toutes les valeurs qu'il a pu atteindre (phase Mark) * puis parcourt linéairement toute la mémoire pour effacer les valeurs non marquées (phase Sweep) * copiant : Toutes les valeurs vivantes sont parcourues et ré-allouées ailleurs (dans le cas d'OCaml dans le tas majeur) * compactant : quand le tas (majeur pour OCaml) devient trop fragmenté, une passe déplace toutes les valeurs pour former un bloc compact. * incrémental : (par opposition à 'stop-the-world') Le travail du GC est découpé en petites phases qui peuvent être faites au milieu de l'exécution du programme, permettant d'éviter des trop longues pauses du GC. Ce choix de caractéristiques permet d'être très efficace et d'avoir des programmes réactifs. En effet, les algorithmes traçant et copiant n'ont pas les même avantages: Le copiant est très rapide. Il ne doit traverser que les données vivantes, mais surtout, il n'y a jamais de fragmentation de la mémoire libre. Une allocation peut donc se faire très simplement : incrémenter un pointeur de la taille de l'objet à allouer, et vérifier s'il y a encore de la mémoire libre. Par exemple sur x86 cela se fait en 3 instructions dont 1 test que le processeur prédit correctement. C'est équivalent au coût d'une allocation sur la pile en C. De plus, comme seules les valeurs vivantes sont traversées, il n'y a aucun coût de nettoyage lié aux valeurs mortes. L'inconvénient majeur du GC copiant est d'utiliser une quantité de mémoire qui est le double de celle réellement allouée par le programme. Le GC traçant est beaucoup moins efficace, mais par contre n'utilise pas beaucoup plus de mémoire que nécessaire. Combiner les deux permet (au coût d'une complexité d'implémentation assez accrue) de bénéficier du faible coût d'allocation du GC copiant pour toutes les données temporaires. L'immense majorité des valeurs allouées dans un programme n'est utile que très peu de temps, et n'a donc pas besoin d'être copiée dans la vielle génération, où le coût d'allocation est élevé. Les changements d'OCaml 4.03 permettent principalement de mieux découper les tranches de travail du GC traçant, pour que le temps de pause à chaque incrément soit à peu près uniforme. C'est particulièrement utile pour ne pas avoir de grosses latences qui apparaissent pile au mauvais moment. Par exemple dans le cas d'une application qui doit faire du rendu à une fréquence fixe (comme un jeu), ou un serveur (cette modification a été financée par une entreprise de trading à haute fréquence). Un autre ajout permet aussi d'utiliser, sous Linux, les grandes page mémoire, pour réduire les coûts liés à la gestion de ces pages ( [[Translation_lookaside_buffer]] ) [Requête de tirage introduisant la modification](https://github.com/ocaml/ocaml/pull/297) [Poste de blog de Jane-street en parlant](https://blogs.janestreet.com/building-a-lower-latency-gc/) #Bibliothèque standard ##Éphémérons Les [éphémérons](https://en.wikipedia.org/wiki/Ephemeron) sont une forme de généralisation des [pointeurs faibles](https://en.wikipedia.org/wiki/Weak_reference) : un pointeur faible est une référence qui n'empêche pas le ramasse-miette de collecter la valeur référée. Ce type de donnée était déjà implémenté par OCaml à travers le module `Weak` de la bibliothèque standard. Il permet par exemple d'implémenter une forme de cache. Un exemple simplifié donnerait : ```OCaml let cache f = let table_faible = Weak.create 1 in let cached_f x = match Weak.get table_faible 0 with | Some (x', y) when x = x' -> y | _ -> let y = f x in Weak.set table_faible 0 (Some (x, y)); y in cached_f ``` Cependant pour certaines utilisations, les pointeurs faibles sont trop limités. Par exemple, on peut essayer de conserver en cache tous les couples entrées-sorties d'une fonction jusqu'au moment où l'entrée est réclamée par le ramasse-miette. Une idée serait de conserver une table faible d'entrées et une table de hachage associant entrée et sortie. Tout se passe bien alors, sauf si la sortie d'une fonction contient une référence à son entrée : ```OCaml let f x = (x, x) ``` Dans ce cas-là, la table entrée-sortie va créer une référence visible par le ramasse-miette sur l'entrée et l'entrée ne sera jamais ramassée tant que la table est en mémoire. Pour pallier ce problème, les éphémérons généralisent la notion de pointeur faible, en associant un ensemble de clefs avec une donnée présente ou non. Cette donnée ne va être conservée par le ramasse-miette que tant que toutes les clefs référencées par l'éphéméron sont en mémoire. De plus, cette donnée associée à l'éphéméron ne compte pas lorsque le ramasse-miette détermine si une des clefs doit être ramassée ou non. Cela nous permet de briser le cycle de références entre clefs et données. Cela nous permet ainsi d'implémenter cette idée de cache entrée-sortie : ```OCaml let cache (type e) f = let module H = struct type t = e let hash = Hashtbl.hash let equal = (=) end in let module Weak_table = Ephemeron.K1.Make(H) in let table = Weak_table.create 0 in let cached_f x = try Weak_table.find table x with | Not_found -> let y = f x in Weak_table.add table x y; y in cached_f ``` ##Types de compatibilité La bibliothèque standard d'OCaml couvre un nombre limité de fonctionnalités. Pour cette raison, il est souvent recommandé de la compléter par une des bibliothèques standard étendues que ce soit [Batteries](http://batteries.forge.ocamlcore.org/), [Core](http://janestreet.github.io/) ou [containers](https://github.com/c-cube/ocaml-containers). Ce modèle basé sur des bibliothèques tierces pose cependant des problèmes d'interopérabilité : il peut être délicat de faire interagir des fonctions ou bibliothèques basées sur des bibliothèques standards étendues différentes. Pour diminuer le nombre de ces incompatibilités, la bibliothèque standard intègre désormais deux types de donnée `('a,'b) result` et `Uchar.t` dans le but de servir de points de synchronisation entre les différentes bibliothèques tierces. Le type `'a result` représente le résultat d'un calcul qui peut réussir ou échouer ```Ocaml type ('a,'b) result = Ok of 'a | Error of 'b ``` tandis que le type `Uchar.t` représente une [valeur unicode scalaire](http://www.unicode.org/glossary/#unicode_scalar_value). Ces deux types sont intégrés plus ou moins tel quel dans la bibliothèque standard. L'implémentation des fonctionnalités associées est laissée aux bibliothèques tierces; qui peuvent désormais s'accorder sur ces types communs. Pour faciliter la compatibilité avec les versions antérieures d'OCaml, ces nouveaux types sont accompagnés de bibliothèques de compatibilités `result` et `uchar` présentes dans les dépôts opam qui exposent une interface uniforme quelle que soit la version d'OCaml. #Améliorations des types algébriques Cette nouvelle version ne se contente pas d'améliorer les performances du code généré, elle apporte aussi son lot d'améliorations en terme de gestion des types algébriques. ##Type enregistrement incorporé (Inline record) En particulier, il est désormais possible de déclarer des types enregistrements au sein de types somme. ```OCaml type 'a arbre = | Feuille of 'a | Noeud of { gauche:'a arbre; droite:'a arbre} ``` Auparavant, il était possible d'écrire : ```OCaml type 'a noeud = {gauche: 'a arbre; droite:'a arbre} type 'a arbre = | Feuille of 'a | Noeud of 'a noeud ``` Cette approche fonctionne mais est moins lisible, en plus de s’étaler sur deux blocks au lieu d'un. Cette nouvelle extension permet non seulement de mélanger plus aisément types somme et types enregistrement, elle peut aussi mener à une représentation mémoire plus efficace. Par exemple, on peut définir une liste mutable ```OCaml type 'a cellule = | Nil | Cons of { contenu: 'a; mutable suivant: 'a cellule } ``` plutôt que passer par une réference ```OCaml type 'a cellule_bis = | Nil | Cons of { contenu: 'a; suivant: 'a cellule_bis ref } ``` ce qui a l'avantage d'éviter une indirection supplémentaire à cause de la référence. Le module `Queue` de la bibliothèque standard a été réécrit pour faire usage de ces nouveaux enregistrements incorporés. Ceci étant, ces types enregistrés incorporés présentent des restrictions et ne sont pas considérés comme des classes primaires. En particulier, ils ne peuvent pas exister en dehors du contexte de leur constructeur. Par conséquent, le filtrage de motif suivant ```ocaml let estunefeuille = function Feuille _ -> None | Noeud x -> Some x ``` échoue en renvoyant _This form is not allowed as the type of the inlined record could escape._ ##Types algébriques généralisés (GADT) Les [types algébriques généralisés](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type) (ou GADT) sont une généralisation des types sommes qui permet d'exprimer des relations relativement complexes entre les constructeurs d'un type, le type des arguments de ce constructeur et le type résultant. Bien utilisés, ces types algébriques généralisés permettent d'encoder dans le système de type lui-même des propriétés importantes des types de données ainsi définis. Par exemple, en utilisant un type somme classique, on pourrait réimplémenter le type `'a list` avec ```OCaml type 'element liste = | Nil | Cons of 'element * 'element liste ``` Si on essaye d'écrire une fonction `premier_element` pour ce type, on se heurte au cas de la liste vide ```OCaml let premier_element l = match l with | Cons (elt, _ ) -> elt | Nil -> (* que faire ici? *) raise Not_found (* émettre une exception peut être une solution *) ``` Avec les types algébriques généralisés, on peut coder directement la longueur de la liste dans le système de type, voire même définir une liste hétérogène. ```OCaml type rien (* un type vide *) type 'elements liste_gadt = | Nil : rien liste_gadt (* la liste vide ne contient rien *) | Cons : 'element * 'elements liste_gadt -> ('element * 'elements) liste_gadt (* Cons prend un nouvel élément et ajoute son type à la liste de types de la liste *) ``` Avec ce nouveau type particulier, on peut maintenant écrire la fonction `premier_element` sans exception : ```OCaml let premier_element ( l : ('element * 'elements) liste_gadt) = match l with | Cons( elt, _ ) -> elt ``` Cette fois-ci, l'appel de fonction `premier_element Nil` renvoie une erreur de type: `Nil` a pour type `rien liste_gadt` et le type `rien` est différent de `'element * 'elements ` et ce quel que soit `'elements`. Il est cependant important de noter que maintenir explicitement la liste de types de la liste dans le système de types introduit beaucoup de rigidité, et ce type `'elements liste_gadt` n'est pas forcément pratique à utiliser hors de cas jouets. Cependant, il permet de mettre en évidence les interactions complexes entre les types algébriques généralisés et le filtrage de motif. ###Motifs réfutables Une des propriétés importantes du filtrage de motifs dans OCaml est que le compilateur peut vérifier que tout filtrage de motifs est à la fois exhaustif (tous les cas sont couverts), et non-redondant (toutes les branches dans le filtrage de motifs sont atteignables). Par exemple, pour le type somme simple `'elt liste`, le filtrage de motif ```OCaml let premier_element (l:'elt liste) = match l with | Cons (elt, _ ) -> elt ``` n'est pas exhaustif, tandis que ```OCaml let premier_element (l:'elt liste) = match l with | Nil -> raise Not_found | Cons( elt, _ ) -> elt | Cons( elt, Cons( snd_elt, _ ) ) -> elt ``` est redondant parce que la troisième branche couvre un cas déjà traité par la seconde. En présence de types algébriques généralisés, déterminer qu'un filtrage de motifs est à la fois exhaustif et non-redondant devient extrêmement complexe, voire indécidable (cf le résumé en pdf [GADTs and exhaustiveness: looking for the impossible](http://www.math.nagoya-u.ac.jp/~garrigue/papers/gadtspm.pdf)). De ce fait, le compilateur préfère émettre des avertissements lorsqu'il ne peut pas prouver qu'un filtrage de motifs est exhaustif. Cela peut mener à des faux positifs, et le compilateur peut alors suggérer des motifs qui sont en fait inatteignables. Pour pallier cette situation, OCaml 4.03 introduit la possibilité d'écrire des motifs réfutables, afin de demander au compilateur de faire plus d'efforts pour prouver que le motif est inatteignable. Par exemple, une situation dans laquelle le compilateur émet des faux positifs apparaît si on construit un deuxième type de liste hétérogène qui possède au minimum un élément. ```OCaml type 'elements liste_gadt_2 = | Premier_element: 'elt -> ('elt * rien) liste_gadt_2 | Cons: 'elt * 'elements liste_gadt_2 -> ('elt * 'elements) liste_gadt_2 ``` On peut ensuite essayer de comparer deux listes de ces deux types différents comme suit : ```OCaml let rec egal : type elements. elements liste_gadt -> elements liste_gadt_2 -> bool = fun l l' -> match l, l' with | Cons(x, Nil), Premier_element y -> x = y | Cons(x,l), Cons(y, l') -> x = y && egal l l' ``` Pour cette fonction, le compilateur se plaint que le filtrage de motif n'est pas exhaustif parce que le cas `(Nil, _ )` n'est pas couvert. Or ce cas est impossible, puisque la liste des types éléments des deux listes sont les mêmes, donc forcément la première liste a au moins un élément. Depuis 4.03, il est possible de dire au compilateur de revoir sa copie en lui indiquant qu'il devrait pouvoir prouver que ce motif est réfutable ```OCaml let rec egal : type elements. elements liste_gadt -> elements liste_gadt_2 -> bool = fun l l' -> match l, l' with | Cons(x, Nil), Premier_element y -> x = y | Cons(x,l), Cons'(y, l') -> x = y && egal l l' | Nil, _ -> . ``` Ici, la dernière clause se termine par un point `.`, pour indiquer que la clause est réfutable et n'est là que pour aider le compilateur à prouver ce point. Un point important à noter est qu'une des raisons pour lesquelles l'exemple précédent est relativement complexe est que la détection de motifs réfutables a été fortement améliorée dans le compilateur, et il est relativement difficile de trouver des cas de faux positifs simples dans un usage "courant" des types algébriques généralisés. ###Messages d'erreur améliorés pour les GADT Les types algébriques généralisés sont clairement une des fonctionnalités les plus complexes d'OCaml. Les erreurs de type générées par un mauvais usage de ces types peuvent être particulièrement ésotériques. Un des problèmes rencontrés pour générer des erreurs compréhensibles provient des types existentiels : un type existentiel est une variable de type qui apparaît dans le type d'un argument d'un constructeur GADT sans apparaître dans le type du GADT lui-même. Un exemple de type existentiel serait le paramètre `'a' dans le type de donnée suivant: ```OCaml type affichable = Affichable: 'a * ('a -> unit) -> affichable let afficher (Affichable (elt,print)) = print elt ``` Dans ce type `affichable`, le constructeur `Affichable` prend en argument un élément de type `a` et une fonction d'affichage `f` pour ce type de donnée et renvoie une valeur de type `affichable`. Le paramètre de type `'a` a donc disparu lors de l'application du constructeur `Affichable`: c'est donc un type existentiel. Ce nom de type existentiel provient du fait que si on considère une valeur `Affichable(elt,print)`, on sait qu'il existe un type `'a` tel que `elt` soit de type `'a` et `print` soit de type `'a -> unit`. Cependant, l'information précise sur quel est ce type `'a` a été complètement perdue. Une des conséquences de cette perte d'information est que ce type existentiel `'a` ne peut échapper le contexte du constructeur. La fonction suivante est par exemple invalide : ```OCaml let escape (Affichable(a,f)) = a ``` En effet, le système de type d'OCaml n'a pas assez d'information pour assigner un type à `a`: il sait qu'il existe un type `t` tel que `a:t` et `f:t->unit`, mais n'a aucune autre information sur `t`. Dans les versions précédentes d'OCaml cela donnait lieu à l'erreur suivante ``` Error: This expression has type a#0 but an expression was expected of type a#0 The type constructor a#0 would escape its scope ``` Cette erreur est particulièrement difficile à comprendre pour le non-initié. Dans ce message d'erreur le compilateur essaye de dire qu'il a initialisé un type existentiel qui était appelé `'a` dans la définition du type GADT avec le type `a#0`. Une erreur est ensuite apparue lorsqu'il a essayé d'unifier le type existentiel `a#0` avec le type non-existentiel `a#0` ce qui n'est pas autorisé parce que cela permettrait au type existentiel `a#0` d'échapper au contexte du constructeur. Pour éviter ces messages sibyllins, la nomenclature utilisée par le compilateur pour nommer les types existentiels a été substantiellement améliorée dans OCaml 4.03; même si elle est encore loin d'être parfaite. * Premièrement, les noms de type existentiel, et uniquement eux, sont préfixés par `$` * Un type nommé `$Constr_'a` correspond à un type existentiel introduit pour la variable de type nommée `'a` dans la définition du constructeur `Constr`. L'erreur de type pour la fonction `escape` précédente devient donc ```OCaml type affichable = Affichable: 'a * ('a -> unit) -> affichable let escape (Affichable(a,f)) = a (* Error: This expression has type $Affichable_'a but an expression was expected of type 'a The type constructor $Affichable_'a would escape its scope *) ``` Par rapport à la version précédente, le message d'erreur fait clairement la distinction entre le type existentiel `$Affichable_'a$` et le type non-existentiel `'a` qui étaient tous deux nommés `$a#0` précédemment. L'origine du type existentiel `$Affichable_'a` est aussi beaucoup plus claire. * Le nom `$Constr` est utilisé si la variable de type correspondante dans la définition du constructeur `Constr` était anonyme: ```OCaml type any = Any : _ -> any let escape (Any x) = x;; (* Error: This expression has type $Any but an expression was expected of type 'a The type constructor $Any would escape its scope *) ``` * Le nom `$'a` est utilisé si une variable de type existentiel sans nom précis a été unifiée avec une variable de type normale `'a` au moment du typage. Par exemple: ```OCaml type ('arg,'result,'aux) fn = | Fun: ('a ->'b) -> ('a,'b,unit) fn | Mem1: ('a ->'b) * 'a * 'b -> ('a, 'b, 'a * 'b) fn let apply: ('arg,'result, _ ) fn -> 'arg -> 'result = fun f x -> match f with | Fun f -> f x | Mem1 (f,y,fy) -> if x = y then fy else f x;; (* Error: This pattern matches values of type ($'arg, $'result, $'arg * $'result) fn but a pattern was expected which matches values of type ($'arg, $'result, unit) fn Type $'arg * $'result is not compatible with type unit *) ``` * Enfin si toutes les méthodes précédentes n'ont pas réussi à donner un nom plus spéficique à un type existentiel, le compilateur utilise simplement `$n` avec n un entier. #Vers le futur ##Plus de flambda L'introduction de la nouvelle représentation intermédiaire est une première étape avant la mise en place de nombreuses nouvelle optimisations. ##Passage à un cycle de versions plus court OCaml a toujours eu un cycle de versions assez long où les versions étaient sorties « quand ça se stabilise ». Certains utilisateurs industriels en sont arrivés au point d'utiliser en production la version de développement. C'est un choix qui n'est pas complètement déraisonnable, celle-ci étant très peu souvent cassée, mais cela reflète néanmoins un problème. Suite à de nombreuses discussions au sein de l'équipe de développement, il a donc été décidé de passer à un cycle avec des versions à des dates presque fixes et beaucoup plus court. Le gel de la version 4.04 est prévu pour cet été. ##Operateurs d'indexation Non content de disposer de deux opérateurs d'additions dans la bibliothèque standard, OCaml se démarque également par son nombre de constructions syntaxiques pour accéder à un élément d'un tableau. En fonction de la famille de type auquel appartient le tableau, il faut utiliser: * `a.(n)` pour les tableaux (type `'a array`) * `a.[n]` pour les chaînes de caractère (type `string`) * `a.{k,l}` pour les tableaux multidimensionnels (type `('a,'b,'c) Bigarray.t` De plus, ces 'opérateurs d'indexation' ne sont pas vraiment des objets de première classes dans le language OCaml. Il n'est pas vraiment possible de réutiliser ces opérateurs sans casse. Pour pallier cet état de fait, une proposition était de transformer ces opérateurs d'indexation en objet de première classe. Cela permettrait par exemple d'avoir une syntaxe à la python pour des dictionnaires ```OCaml let (.{}) dict x = Hashtbl.find dict x let (.{}<-) dict x y = Hashtbl.add dict x y let dict = Hashtbl.create 10 ;; dict.{"key"}<-0 ;; dict.{"key"} (* renvoie 0 *) ``` Cette proposition a cependant le désavantage qu'elle ne réduit pas le nombre d'opérateurs d'indexation, elle ne fait qu'accroître leur flexibilité. Elle s'est retrouvée en conflit, avec une seconde proposition dont le but était de réduire le nombre d'opérateurs d'indexation et de détrôner les tableaux `float array` de leur rôle particulier au sein du compilateur. L'idée de cette seconde proposition est de définir une famille de types pour les tableaux, pour laquelle il serait possible d'utiliser la syntaxe `a.(n)` et ce quelque soit le type précis du tableau. Les deux propositions s'étant retrouvées en conflit à la fin de la période d'intégration pour la version 4.03, il a été décidé de prendre le temps pour réfléchir posément à leur éventuelle intégration dans une future version. ##Multicœur Il y a une demande assez forte depuis longtemps pour un glaneur de cellule (GC) multicœur, mais malgré plusieurs prototypes abandonnés avec le temps ceci n'a jamais été intégré à OCaml. Un nouveau projet, lancé depuis environ 2 ans à Cambridge chez [OCamllabs](https://github.com/ocamllabs/ocaml-multicore.git) semble arriver à un point de maturité suffisante pour pouvoir être sérieusement testé. Ce développement est long, et est encore assez loin d'être terminé. C'est déjà un travail vraiment impressionnant, sachant que leur objectif (qu'il faut atteindre pour avoir une chance d'être accepté dans la distribution officielle) est d'être quasiment aussi efficace que le GC actuel sur du code séquentiel. C'est ambitieux car celui-ci est particulièrement efficace. ##Implicites modulaires OCaml est un langage très explicite. Cela se remarque en particulier au niveau des types numériques : il n'y a aucune conversion implicite entre entiers et nombres à virgule flottante. De manière similaire, une particularité d'OCaml est que le langage possède des opérateurs numériques séparés pour les entiers (`+`,`-`,`*`,`/`, etc...) et les nombres à virgule flottante (`+.`,`-.`,`*.`,`/.`, etc...). Cette duplication est due à l'absence dans OCaml de [polymorphisme ad-hoc](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism). Deux exemples de polymorphisme ad-hoc seraient la surcharge de fonction à la C++ ou les classes de type à la [[Haskell]]. Les implicites modulaires sont une proposition audacieuse ([pdf](http://www.lpw25.net/ml2014.pdf)) pour implémenter des fonctionnalités comparables aux classes de type à la Haskell en se basant sur les modules d'OCaml. Cette idée, directement inspirée des paramètres implicites utilisés en [Scala](https://fr.wikipedia.org/wiki/Scala_%28langage%29), consiste à pouvoir utiliser des modules comme paramètres implicites de fonction. Par exemple, en Haskell, une fonction `moyenne` pourrait s'écrire ```Haskell moyenne:: Num n => n -> n -> n moyenne x y = (x + y) / 2 ``` Dans cette expression, `Num n` indique que le type n appartient à la classe de type `Num` et par conséquent implémente l'addition `+`, la division `/` et la conversion depuis un entier. En OCaml sans implicites modulaires, il est possible d'émuler ce concept en passant un module `M` de signature `Num` qui contient ces fonctions nécessaires comme argument de la fonction moyenne: ```OCaml let moyenne (type a) (module N:Num with type t = a) x y = N.( x + y / from_int 2 ) ``` Cependant, avec cette définition, il est nécessaire de passer le module `N` à la main à chaque usage de la fonction `moyenne`. Les modules implicites permettent de s'affranchir de cette limitation en passant N comme un module implicite ```OCaml let moyenne {N:Num} x y = x + y / from_int 2 ``` Il devient possible d'appeler `moyenne 2 5` sans préciser le module ( sous conditions que `+`, `/` et `from_int` utilisent également le module implicite N ). Avec cette définition, si les bon modules sont visibles, il est possible d'appliquer `moyenne` à la fois à des entiers et des nombres à virgule flottante sans avoir à repréciser le type. Il serait ainsi possible de se passer d'opérateurs numériques différenciés pour chaque type numérique. Cependant, avec des paramètres implicites, une question importante est de savoir comment le compilateur choisit quels paramètres implicites sont utilisés. L'implémentation proposée pour ces implicites modulaires essaye d'être la plus explicite possible à ce niveau. Par exemple, un module ne peut être utilisé comme paramètre implicite que s'il a été déclaré comme disponible en tant que paramètre implicite. Si cette proposition a été très bien reçue par la communauté OCaml, il reste néanmoins un travail important avant de pouvoir l'intégrer dans le compilateur. Pour tester ces nouvelles possibilités, un fork est disponible sur le [dépot github](https://github.com/def-lkb/ocaml-modular-implicits.git). #Améliorations mineures Cette nouvelle version s'accompagne aussi d'une ribambelle d'améliorations mineures que ce soit en terme d'usabilité, de sucre syntaxiques, de nouveaux avertissements ou encore un support étendu des extensions par points d'extension. En voici une liste longue mais non exhaustive. ##Usabilité ###Sortie en couleur du compilateur Le compilateur affiche désormais les messages d'erreur et d'avertissement en couleur, lorsqu'utilisé au sein d'un terminal qui supporte l'affichage en couleur. ###Directives help pour l'interpréteur interactif L'interpréteur interactif d'ocaml, que ce soit l'interpréteur standard ou sa version enrichie, [utop](https://opam.ocaml.org/blog/about-utop/), dispose d'un certain nombre de directives qui permettent de contrôler le comportement de l'interpréteur ou d'afficher des informations utiles. Ces directives étaient jusqu'à présent uniquement documentées dans le manuel de référence d'OCaml. Il est désormais possible d'utiliser la directive ```OCaml #help;; ``` pour afficher une liste des directives disponibles avec une brève description de leur usage. ###Commentaire de documentation gérés par le compilateur Ceci est un changement de la 4.02.2, maturé dans la 4.03.0. La syntaxe des commentaires en OCaml est `(*` pour commencer un commentaire et `*)` pour le terminer. La convention des commentaires de documentation est, depuis longtemps, de commencer les commentaires avec un `*` de plus: ```OCaml val length : 'a list -> int (** Return the length (number of elements) of the given list. *) ``` C'est la forme des commentaires reconnue par l'outil OCamldoc qui génère les versions html des [documentations](http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html). Le compilateur est donc maintenant aussi capable de comprendre cette convention, ces commentaires sont gardés dans les arbres de syntaxe, et en particulier sont disponibles dans les fichiers `.cmt`. Cela permet d'écrire plus simplement de meilleurs outils pour gérer la documentation. Par exemple [ocp-index](https://github.com/OCamlPro/ocp-index) peut retrouver la documentation d'une fonction en même temps que son type. Un nouvel outil est en train de voir le jour pour remplacer OCamldoc, utilisant les informations de liaison et de typage pour gérer, entre autres, les liens entre modules de différents projets et les instanciations de foncteurs. [Par exemple, cette documentation de la bibliothèque core de Janestreet](https://ocaml.janestreet.com/ocaml-core/latest/doc/core/). ##Régularisation des constructeurs (::) et [] les constructeurs `::` est `[]` étaient des exceptions gérés différemment des autres types algébriques. Par souci de régularisation ils est maintenant possible de les redéfinir. Ce n'est pas forcement une très bonne pratique, mais ça peut avoir des usages pour faire des DSL. Un exemple hautement inutile: les listes de longueur paire et impaire. ```OCaml type 'a liste_impaire = | (::) of 'a * 'a liste_paire and 'a liste_paire = | [] | (::) of 'a * 'a liste_impaire let a : int list_impaire = 1 :: [] let b : int list_paire = 2 :: 1 :: [] ``` Notez en particulier que comme la syntaxe `[1;2]` est un sucre syntaxique pour `1 :: 2 :: []`, il est donc possible d'écrire: ``` let a : int list_impaire = [1] let b : int list_paire = [2;1] ``` ##Améliorations mineures de la syntaxe Cette nouvelle version introduit aussi un certain nombre d'améliorations mineures de la syntaxe, offrant des raccourcis utiles dans certaines situations * omissions simplifiées de variables de type dans les types paramétrés Il peut arriver que l'on ne souhaite pas préciser les paramètres d'un type paramétré. Une solution dans cette utilisation était d'utiliser un tiret bas `_`, par exemple `(_,_) result`. Cependant, cela demandait d'utiliser autant de tirets que de paramètres de type. Il est désormais possible d'utiliser `_` sans ce soucier du nombre de paramètre: ` _ result` * syntaxe non dépendante pour les foncteurs : S1 -> S2 ```OCaml module type F = S -> S' (* plutôt que *) module type F = functor (M:S) -> S' ``` Cette nouvelle syntaxe pour les foncteurs est plus légères mais ne couvre pas toutes les possibilités du système de modules, par exemple : ```OCaml module type F = functor (M:S) -> (S' with type t = M.z) ``` * annotations simplifiées de type sur les champs d'un enregistrement Dans les rares cas où cela est utile, le type d'un champ peut être annoté directement sur l'étiquette associée ```OCaml type ('gauche,'droite) paire = { gauche:'gauche; droite:'droite} {x:int list=[]; y:float list=[]};; (* plutôt que *) { x = ([]:int list); y = ([]:float list) } ``` * annotation simplifiée pour le type retour d'une fonction ```OCaml let f = fun x y : int list -> [x;y] (* remplace *) let f = fun x y -> ([x;y] : int list) ``` * punning pour la copie d'objets ```OCaml object val x=0 val y = 1 method update x = {< x>} end ``` à la place de ```method update x = {< x = x>}``` * raccourci pour types localement abstraits multiples ```OCaml let f (type a b) (x:a) (y:b) = () (* plutôt que *) let f (type a) (type b) (x:a) (y:b) = () ``` * Notation hexadécimale pour les nombres à virgule flottante. Les nombres à virgules flottantes peuvent désormais être écrits en notation hexadécimale, ce qui peut être utile pour le calcul numérique. ```OCaml let quinze = 0xF. let demi = 0xp-1 ``` * Notation octale pour les caractères Il est désormais possible d'écrire des séquences d'échappements en octal au sein des chaîne de caractères: ```OCaml let neuf = "\o071";; ``` ##Avertissement sur les filtrages fragiles Cet nouvel avertissement un peu particulier peut être contrôlé à travers un attribut prédéfini `[@warn_on_literal_pattern]`. Il permet de marquer l'argument d'un constructeur d'un type somme comme étant là à but purement informatif. Tout usage du [filtrage de motif](https://fr.wikipedia.org/wiki/Filtrage_par_motif) sur cet argument émet un avertissement pour signaler qu'il n'y a aucune garantie que l'argument soit stable. En particulier, la bibliothèque standard utilise cet avertissement pour indiquer que certains messages renvoyés par des exceptions sont purement informatifs. L’intérêt principal est d'aider à l'écriture de code plus facilement maintenable dans le temps en empêchant les utilisateurs de dépendre du texte précis d'un message, alors que celui-là pourrait potentiellement subir une correction orthographique. ## Extensions de syntaxe par point d'extensions Apparues dans la version 4.02, les extensions de syntaxe par points d'extension ou extensions ppx implémentent un nouveau mécanisme pour l'écriture d'extensions de syntaxe pour OCaml. Ce mécanisme a pour but d'apporter une alternative plus simple, composable et rapide aux préprocesseurs camlp4/5. La différence fondamentale entre extensions ppx et camlp4/5 est le point application de ces extensions. Les extensions basées sur les préprocesseurs campl4/5 transforment un code source en du code ocaml au niveau textuel: code source X → extension campl4 X → code source OCaml Cela permet notamment de réécrire totalement la grammaire et syntaxe d'OCaml. Cependant, cela pose des problèmes de composabilité. Si deux extensions campl4 `A` et `B` implémentent deux langages `OCaml + A` et `OCaml + B`, combiner ces deux extensions nécessitent d'écrire une nouvelle extension `A+B` afin de pouvoir reconnaître le langage `OCaml + A + B`. De plus, puisqu'une extension campl4 transforme un code source écrit dans le nouveau langage en code source ocaml, le compilateur a besoin de re-analyser la sortie de l'extension camlp4. Les points d'extension ont pour but d'éviter ces écueils des extensions camlp4 en partant du principe que la majorité des extensions de syntaxe n'ont pas besoin de réécrire complètement la grammaire d' OCaml. L'idée de base des extensions ppx est de laisser le travail d'analyse syntactique au compilateur OCaml et d'implémenter les extensions ppx comme des transformations au niveau de l'arbre syntaxique abstraite d'OCaml. Puisqu'ils travaillent au niveau de la syntaxe abstraite d'OCaml, les extensions ppx ne peuvent donc plus complètement transformer la syntaxe d'OCaml mais peuvent uniquement réinterpréter la syntaxe existante. Cette restriction garantit que les extensions ppx peuvent travailler ensemble et que l'utilisateur n'a pas besoin de réapprendre une syntaxe complètement différente pour chaque extension. Cependant, pour donner plus de marge de manœuvres aux extensions ppx, l'analyseur syntaxique d'OCaml reconnaît et transforme en arbre syntaxique abstraite valide une grammaire légèrement plus étendue que la grammaire OCaml vanilla. En particulier, les points d'extensions de la forme ```OCaml let f x = [%extension expression] x [%%extension phrase ] ``` représente des nœuds de l'arbre syntaxique abstraite étendue qui doivent être transformés en nœuds valides par un transformateur ppx sous peine d'erreur du compilateur. Ces nœuds d'extensions peuvent ensuite être complémentés par par des attributs `[@attributs]` qui associent des métadonnées aux nœuds de l'arbre syntaxique abstraites et des chaînes de caractères bruts `{delimiteur| ""''"" |delimiteur}` qui permettent de s'affranchir de la syntaxe d'OCaml de manière délimitée. Avec cette structure, il est bien plus facile de composer les extensions ppx : une extension donnée n'a besoin que de modifier ses nœuds d'extensions en laissant intact le reste de l'arbre syntaxique. Les extensions ppx peuvent donc être appliquées en série par le compilateur, avec un nombre limité de conflits potentiels entre extensions. À la fin de cette série de transformations, le compilateur n'a plus qu'à vérifier que l'arbre syntaxique abstraite obtenue correspond bien en un arbre syntaxique abstraite vanilla: OCaml + ppx → analyseur syntactique OCaml → AST étendu → extensions ppx → AST étendu → vérification de l'AST → compilation standard Les avantages par rapport au modèle camlp4 sont donc l'utilisation d'une seule grammaire `OCaml + ppx` en entrée pour le compilateur, la possibilité de chaîner les extensions de syntaxe puisqu'elles travaillent sur la même représentation et la possibilité d'analyser qu'une seule fois le code source. Un des inconvénients des extensions ppx vient du fait qu'elles doivent partager la même syntaxe, ce qui peut mener à certaines lourdeurs syntactiques. Pour faciliter l'utilisation de ces extensions, de nouvelles constructions ont été ajoutées à la grammaire étendue d'OCaml depuis la version 4.02. ###Raccourcis d'extension Dans OCaml 4.02.3, certains mot-clefs disposaient d'une syntaxe raccourcie pour les nœuds d'extensions commençant à ce mot-clef: ```OCaml let%extension f x = () (* équivaut à *) [%%extension let f x= () ] ``` Ces raccourcis ont été généralisés à tous les mots-clefs pour lesquels un tel sucre syntaxique fait sens. ###Opérateurs d'extension Depuis la version 4.02.3, certains opérateurs sont réservés pour les extensions ppx. Il s'agit des opérateurs commençant par un croisillon `#` et contenant au moins deux croisillons `#`: ```OCaml let ( ##% ) f x = f x ``` Il est à noter que la définition d'opérateurs commençant par des croisillons en général n'est possible que depuis 4.02.3. Ces opérateurs présentent l'intérêt de lier plus fortement que l'application de fonction. Cette forte précédence est exploitée par exemple par l'extension de syntaxe de javascript_of_ocaml pour implémenter l'appel de méthode sur les objets javascripst. ###Littéraux d'extension Dans OCaml vanilla, il existe quatre classes différents de litéraux pour les entiers: `1` pour les entiers marqués utilisés par OCaml, `1n` pour les entiers natifs non marqués, `1l` pour les entiers 32 bits et `1L` pour les entiers 64 bits. De la même manière, les littéraux flottants représentent forcément des flottants 64 bits. OCaml 4.03.0 lève cette limitation dans la grammaire étendue ppx. Il est désormais possible d'appliquer un caractère modal (de f à z ou de F à Z) à n'importe quel littéral numérique. De tels littéraux modifiés pourraient par exemple être utilisés pour implémenter une syntaxe plus naturelle pour les quaternions ou complexes ```OCaml let quaternion = 1.0i + 2.0j + 3.0r let entier_de_gauss = 1i + 2r ``` Ces littéraux modifiés ne sont cependant valides que dans la grammaire étendue et doivent être traduits en une forme syntactique vanilla par une extension ppx pour être utiles. ###Nœud d'extension prédéfini Une nouveauté relativement surprenante est l'apparition d'un nœud d'extension prédéfini `[%extension_constructor]` dans le compilateur. Originellement, les nœuds d'extension ont été introduits pour permettre aux extensions ppx d'étendre de façon contrôlée la syntaxe d'OCaml. Ce nœud d'extension inclus au sein du compilateur lui-même est donc relativement exotique. Il s'applique dans des cas particuliers en lien avec les types sommes ouverts introduits avec OCaml 4.02 et permet de calculer le numéro d'un constructeur ajouté au type somme: ```OCaml type t = .. type t += X of int | Y of string let x = [%extension_constructor X] let y = [%extension_constructor Y] let b = xy;; - : b = true ``` Pourquoi utiliser un nœud d'extension ici ? Simplement parce que cela permet de ne pas avoir à construire une valeur valide du type `t` pour pouvoir demander au compilateur le numéro assigné au constructeur. Cette construction a été introduite pour pouvoir gérer les types extensibles dans les extensions ppx gérant la sérialisation/dé-sérialisation. C'est à priori inutile pour un usage 'normal' du langage. #Remerciements Cette dépêche collaborative n'aurait pas été possible sans le concours de kantien, chicco, Lucas, VictorAche, BAud, Ontologia, Nicolas Boulay et les corrections de Yves Bourguignon, gim, Snark, Nÿco, Nicolas Casanova, Storm, ɹǝıʌıʃO, KlakPlok, anaseto, come, eggman, theblatte, Anthony Jaguenaud, Anonyme, patrick_g, Dinosaure.

AltStyle によって変換されたページ (->オリジナル) /