URL: https://linuxfr.org/news/ocaml-4-02 Title: OCaml 4.02 Authors: chimrod octachron, jumbojet, GNU_Eths, Ontologia, Snark, Michaël, BAud, Benoît Sibaud, palm123, Def, Pierre Jarillon, Tonton Th et Bruno Michel Date: 2014年08月29日T19:26:08+02:00 License: CC By-SA Tags: ocaml, fortran et postgresql Score: 48 La version 4.02 du langage OCaml a été annoncée vendredi 29 août. Elle fait suite à la version 4.01 publiée en septembre 2013. Il s'agit de l'implémentation la plus vivace du langage ML (_[[Meta language]]_) et l'un des langages fonctionnels les plus utilisés (comme Haskell, XSL, Lisp...) ---- [Annonce de la sortie](https://sympa.inria.fr/sympa/arc/caml-list/2014-08/msg00127.html) [Dépêche de la sortie 4.0](https://linuxfr.org/news/ocaml-4-00-0-est-sorti) [Guide d'écriture d' extensions ppx](http://whitequark.org/blog/2014/04/16/a-guide-to-extension-points-in-ocaml) [Résumé de la rencontre OCaml 2014](http://www.syslog.cl.cam.ac.uk/2014/09/05/ocaml-2014/) ---- Ce changement mineur de version n'est pas représentatif des évolutions présentes. Cette version inclut des changements dans la syntaxe du langage et des outils, pouvant casser la compatibilité avec les programmes existants. Les changements d'outils et de bibliothèque ======================================== Points d'extensions et séparation de Camlp4 ---------------------------------------- L'une des évolutions les plus attendues modifie le paradigme de méta-programmation d'OCaml en introduisant une étape de compilation permettant de transformer l'arbre syntaxique du programme, un mécanisme analogue1 aux macros Lisps. 1 Mais seulement *analogue* ! Le paradigme de méta-programmation précédent s'appuyait sur l'historique pré-processeur camlp4 qui permettait de définir des dialectes d'OCaml en enrichissant sa syntaxe. Les applications principales de ce type de pré-processeur sont : * La représentation de données externes dans le système de types du programme ce qui permet de garantir à la compilation que l'ensemble des types sont cohérents entre eux. La [bibliothèque PG'OCaml](http://pgocaml.forge.ocamlcore.org/) permet ainsi de vérifier que la cohérence de type des requêtes SQL avec le code OCaml à la compilation. * La génération automatique de fonctions de sérialisation et déserialisation de structures de données, ce qu'implémente la [sexplib](https://github.com/janestreet/sexplib#sexplib---s-expressions-with-type-converters-for-ocaml). * La création de sous-langages propres à un [[langage dédié]], ceci est utilisé par le serveur web OCsigen de sorte qu'un document HTML généré par un programme OCaml/OCsigen valide est valide. Les principaux défauts de `camlp4` sont sa complexité et l'augmentation du temps de compilation que son utilisation implique : un programme traité par `camlp4` passe une fois dans l'analyseur syntaxique de `camlp4` puis le texte correspondant passe dans l'analyseur syntaxique du compilateur `ocaml`. De plus, `camlp4` faisant partie de la distribution d'`ocaml` les mises à jour ne pouvaient se faire que de pair avec celles du compilateur. Ce type de dépendance ralentit la correction et cause des bogues et des [incompatibilités](https://sympa.inria.fr/sympa/arc/caml-list/2014-08/msg00058.html), parfois subtiles, avec la syntaxe d'OCaml. Si le dernier point devrait s'améliorer grâce à la séparation de `ocamlp4` du compilateur, les autres restent d'actualité. Le nouveau paradigme de méta-programmation faisant son arrivée dans OCaml est les *points d'extensions*, qui sont des éléments syntaxiques dont la forme est définie par le langage mais dont la sémantique est définie par un pré-processeur. Souvenons-nous que lors de la compilation d'un programme, les instructions sont transformées dans un [[arbre syntaxique abstrait]] que le compilateur traduit en code machine — après d'éventuelles optimisations. Comme le langage OCaml se compile lui-même, la structure de cet arbre est une structure de donnée OCaml. Il est maintenant possible de modifier la structure de cet arbre à travers les fameux points d'extensions. L'intérêt premier est de travailler sur un objet déjà traité et construit par le compilateur, ce qui limite les erreurs et facilite l'écriture des extensions. On y gagne aussi en vitesse d'exécution puisque l'étape d'extension se fait dans le processus de compilation, et non pas via un appel externe. Enfin, puisque les points d'extensions ne modifient pas la syntaxe d'OCaml, faire cohabiter différentes extensions devrait être plus facile. Aujourd'hui `camlp4` est encore très largement utilisé. Le sortir du cœur d'OCaml ne va pas le faire disparaître immédiatement, car de nombreux modules n'ont pas encore leur équivalent via les points d'extensions. On peut toutefois maintenant supposer qu'il va mourir de sa belle mort, après avoir été utilisé pendant de nombreuses années et avoir été une figure majeure de l'univers OCaml. Le projet est désormais hébergé sur [github](https://github.com/ocaml/camlp4), ce qui marque sa séparation d'avec le cœur d'OCaml. (Par contre les amateurs de Lisp seront déçus : cet accès au code est limité au temps de la compilation, et le programme compilé n'a pas la possibilité de se modifier lui-même. Cela n'est possible qu'à travers un programme tiers.) Séparation de Labltk -------------------- La bibliothèque `Labltk` était jusqu'à présent une partie intégrante de la distribution OCaml. Elle sera désormais disponible comme une bibliothèque tierce. Nouvelles extensions au langage =============================== Exceptions et filtrage par motif -------------------------------- La gestion des exceptions est très performante en OCaml. En compilant le programme avec les bonnes options, une exception est transformée en une simple instruction de saut, et est souvent utilisée pour sortir d'un traitement (boucle, etc). Cet usage des exceptions est maintenant mieux intégré dans le langage puisqu'une exception peut être traitée comme une valeur de retour et être intégrée dans un filtrage par motif. En effet, l'utilisation d'exceptions dans du code récursif pouvait poser problème. Voici un petit exemple qui illustre la situation : le programme suivant permet de lire un fichier ligne par ligne et stocker l'ensemble des lignes dans une liste : ```OCaml let read_lines inc = let rec loop acc = (* Schéma classique de récursion terminale à travers un accumulateur *) try let l = input_line inc in loop (l :: acc) (* Appel récursif en ajoutant la ligne lue à l'accumulateur *) with End_of_file -> List.rev acc (* Renvoie la liste accumulée quand on atteint la fin du fichier *) in loop [] ``` Sauf que l'appel à la fonction loop est présent dans un gestionnaire d'exception, ce qui empêche la fonction d'être optimisée en récursion terminale. Sur un fichier de taille suffisamment importante, le programme va saturer la pile. Il est désormais possible d'utiliser la syntaxe suivante, qui considère que l'exception est une valeur de retour possible de la fonction : ```OCaml let read_lines inc = let rec loop acc = match input_line inc with | l -> loop (l :: acc) (* Appel récursif en ajoutant la ligne lue à l'accumulateur *) | exception End_of_file -> List.rev acc (* Renvoie la liste accumulée quand on atteint la fin du fichier *) in loop [] ``` Dans cette nouvelle syntaxe, l'exception est devenue un type de retour de la fonction qu'il est possible d'intégrer dans le filtrage par motif. De cette manière, il n'est plus nécessaire de mettre en place un bloc `try` ... `with` et la fonction peut maintenant être optimisée par le compilateur. Cette nouvelle approche a, de plus, l'avantage d'être plus lisible et beaucoup plus concise. Modifications du système de module ----------------------------------- Les langages de la famille ML ont un riche système de modules qui se distingue notamment par sa notion de foncteur. Un module est une sorte de paquetage contenant des définitions de type, parfois totalement abstraites, et les fonctions agissant sur ces types qui vont avec. Autrement appelés modules paramétrés, les foncteurs sont aux modules ce que les fonctions sont aux valeurs. Tout comme une fonction construit une nouvelle valeur à partir de valeurs paramètres, un module paramétré permet de construire un nouveau module à partir de modules déjà construits. On peut faire une analogie avec la généricité dans le monde objet. Cependant, cette richesse peut entraîner des comportements non-intuitifs pour l'utilisateur. Cette nouvelle version du compilateur corrige trois problèmes fréquemment rencontrés avec le système de modules. ###Alias de module### Notamment, dans les versions antérieures d'OCaml, la pratique courante de définir un alias court pour un module ```Ocaml module S = Module_with_a_very_long_name ``` avait pour effet de créer une nouvelle copie du module `Module_with_a_very_long_name`. Cette copie inutile augmentait parfois fortement la taille des exécutables générés. Dans cette nouvelle mouture du compilateur, le module `S` est défini comme un simple alias évitant cette copie potentiellement coûteuse. ### Foncteur génératif### Un autre exemple de comportement non-intuitif apparaissait lors de l'utilisation de foncteur pour générer des modules uniques. Imaginons par exemple que nous souhaitons générer plusieurs types uniques d'identifiants avec un foncteur de la forme : ```Ocaml module MakeId (M : sig end ) : sig type t ... end = struct type t = int ... end ``` On peut alors construire des nouveaux modules d'identifiants avec : ```Ocaml module Id1 = MakeId(struct end) module Id2 = MakeId(struct end) ``` Les deux types `Id1.t` et `Id2.t` sont alors distincts et l'on ne peut pas mélanger les deux types d'identifiants. Cependant écrire à chaque fois un module vide est relativement pénible. Une idée serait d'écrire plutôt : ```Ocaml module E = struct end module Id1 = MakeId(E) module Id2 = MakeId(E) ``` Malheureusement, avec cette définition les deux modules générés sont identiques car générés à partir du même module `E`. La nouvelle extension sur les foncteurs génératifs simplifie ce cas particulier où le foncteur prend le module "vide" en argument. Avec cette extension on peut écrire plus simplement : ```Ocaml module MakeId () : sig type t ... end = struct type t = int ... end module Id1 = MakeId() module Id2 = MakeId() ``` et obtenir deux types distincts `Id1.t` et `Id2.t`. ### Ouverture locale de modules Afin de faciliter l'utilisation des modules, OCaml, depuis la version 3.12, dispose de plusieurs constructions syntaxiques pour ouvrir localement un module, i.e. importer les identifiants du module dans l'espace de noms courant. Par exemple, pour utiliser l'opérateur `+` pour additionner des `float`, il est possible de définir un module : ```Ocaml module Float = struct let ( + ) = ( +. ) end ``` Ouvrir ce module globalement aurait l'inconvénient de masquer l'addition des entiers, mais on peut l'ouvrir localement ```OCaml let x = let open Float in 1. + 5. let y = Float.( 1. + 5. ) ``` Dans la définition de `y`, le module `Float` est ouvert uniquement à l'intérieur des parenthèses. Cette construction s'est révélée très appréciée et vient d'être étendue pour éviter certains doublons de parenthèses ```OCaml let list_f = Float.[ 1. + 2.; 3. ] (* plutôt que Float.( [ 1. + 2.; 3. ] ) *) let array_f = Float.[| 1. + 2. ; 3. |] (* plutôt que Float.( [| 1. + 2.; 3. |] ) *) type record_t = { content : float} let record = Float.{content = 1. + 2. } (* plutôt que Float.({content = 1. + 2. }) *) ``` ##Types ouverts## Le système de type d'OCaml s'enrichit d'une nouvelle catégorie de type, les types ouverts qui sont à mi-chemin entre les types sommes classiques et les variants polymorphes. ### Petit rappel sur les types enumérés existants #### Types sommes Dans les types sommes, l'ensemble des constructeurs est déterminé lors de la définition du type ```OCaml type card = King | Queen | Jack | As | Suit of int ``` Comme le type est figé, il est facile de vérifier dans un filtrage par motif que tous les cas possibles sont gérés. Par contre une fois cette définition posée, les constructeurs du type `card` sont fixés et rien ne peut les changer. Si on souhaitait définir le type `tarot_card` à partir du type `card`, on serait limité à : ```OCaml type tarot_card = Card of card | Knight ``` ce qui alourdit les notations. Par exemple, étendre la fonction ```OCaml let is_jack card = match card with | Jack -> true | _ -> false ``` au type `tarot_card` nécessite de définir une nouvelle fonction ```OCaml let is_jack_tarot tarot_card = match tarot_card with | Card card -> is_jack card | _ -> false ``` cette expression utilise l'underscore qui, en OCaml, signifie en quelque sorte le _joker_, ou en d'autres termes "pour tous les autres cas, fais ceci" #### Variants polymorphes Les variants polymorphes permettent de s'abstraire de cette limitation et d'utiliser des constructeurs directement sans avoir à les définir au préalable. Par exemple : ```OCaml let seven = `Suit 7 ``` définit une nouvelle variable à partir du constructeur `` `Suit``. Contrairement aux apparences, le constructeur `` `Suit `` n'a rien à voir avec le constructeur `Suit` définit pour le type `card`. Le `` ` `` dénote le constructeur d'un variant polymorphe : n'importe quel nom commençant par une majuscule et précédé par `` ` `` est un constructeur de variant polymorphe classique. Pour l'instant le compilateur sait juste que le constructeur `` `Suit `` fait partie d'un ensemble ouvert qui n'est pas encore complètement défini. Comme pour les types sommes, on peut utiliser le filtrage de motif pour définir des fonctions sur ces variants, par exemple : ```Ocaml let is_jack card = match card with | `Jack -> true | _ -> false ``` Avec cette définition, l'application `` is_jack seven `` est bien typée et retourne `false` comme attendu. Cependant, la fonction `is_jack` peut être appliquée à (presque) n'importe quel variant polymorphe : ``is_jack (`John `Do) `` est aussi bien typé et retourne `false`. Le compilateur utilise un système de type relativement complexe pour vérifier que les expressions impliquant les variants polymorphes sont valides. Par exemple, appliquer la fonction : ```OCaml let value_figure tarot_card = match tarot_card with | `King -> 4.5 | `Queen -> 3.5 | `Knight -> 2.5 |` Jack -> 1.5 ``` à `seven` déclenchera une erreur de type durant la compilation car la fonction `value_figure` ne sait que faire du constructeur `` `Suit``. Cependant, à cause de la flexibilité des variants polymorphes, certaines erreurs ne peuvent pas être détectées par le compilateur. Par exemple : ```OCaml let is_suit card = match card with | `Suit -> true | _ -> false ``` est une fonction OCaml valide, néanmoins ` is_suit seven` déclenche une erreur de type parce que dans la fonction `is_suit`, le constructeur `` `Suit `` n'admet pas d'argument. La bonne version de cette fonction serait : ```OCaml let is_suit card = match card with | `Suit _ -> true | _ -> false ``` mais le compilateur n'a aucun moyen de le deviner. ### L'apport des types ouverts Les types ouverts sont, eux, des types sommes auquels on peut ajouter de nouveaux constructeurs après leur définition. Par exemple, on peut commencer par créer un type ouvert sans constructeur : ```Ocaml type open_card = .. ``` Puis, on peut rajouter les constructeurs de `card` dans un premier temps ```Ocaml type open_card += King | Queen | Jack | As | Suit of int ``` À partir de ce point, on peut réaliser une copie de ce type ouvert ```OCaml type open_tarot_card = open_card = .. ``` et étendre cette copie ```OCaml type open_tarot_card += Knight ``` La fonction ```OCaml let is_jack card = match card with | Jack -> true | _ -> false ``` est alors de type `open_card -> bool` mais accepte en entrée tout type qui est une extension du type `open_card`: `is_jack Knight` est valide et renvoie `false` comme attendu. Un point important dans la fonction précédente est la présence dans le filtrage de motif du motif `_` : le type `open_card` étant ouvert, si on veut être exhaustif, il est nécessaire d'offrir une alternative pour les constructeurs qui ne sont pas encore connus. En cas d'oubli, le compilateur émet un avertissement pour signaler que le filtrage n'est pas exhaustif en présence d'extension. Une différence majeure avec les variants polymorphes est que tous les constructeurs de `open_card` sont connus par le compilateur, qui peut donc plus facilement détecter certaines erreurs. La fonction ```OCaml let is_suit card = match card with | Suit -> true | _ -> false ``` déclenche donc bien une erreur de compilation à cause du mauvais usage du constructeur `Suit` contrairement au cas des variants polymorphes. Un détail amusant est qu'il existait déjà une forme de type ouvert dans les versions antérieures d' OCaml : le type exn des exceptions qu'il était possible d'étendre en déclarant une nouvelle exception ```OCaml exception Open ``` Avec l'apparition des types ouverts dans la version principale du compilateur, le type des exceptions est devenu un cas particulier de type ouvert. Rappel sur les types algébriques généralisés ============================================ Introduits dans la version 4.0 il y a deux ans, les types algébriques généralisés (GADT) sont une évolution importante apportant plus d'expressivité au système de types. Ce type de données permet de structurer un type somme en spécifiant quelles opérations sont autorisées sur chacune des valeurs possibles. Le but est bien sûr de donner plus de sécurité au programme en vérifiant dès la compilation qu'une action peut s'appliquer pour une valeur donnée. Par exemple, un [[arbre bicolore]] oblige chaque nœud noir à avoir un parent rouge. Il est possible de vérifier cette règle en contrôlant à chaque opération d'insertion que l'ensemble de la cohérence de l'arbre est respectée. Toutefois, grâce aux GADT, ce contrôle peut être réalisé lors de la compilation du code : la cohérence de la structure de données sera ainsi garantie par le compilateur. Cela permet de : - se reposer sur le compilateur pour garantir la structure de données : une erreur dans l'implémentation sera détectée au plus tôt. - accélérer la vitesse d'exécution du code : puisque la structure est garantie par le compilateur, il n'est pas nécessaire de contrôler chaque opération à l'exécution. ## Avant les GADT Imaginons que nous nous intéressons à des expressions mélangeant des entiers, des booléens et les opérations usuelles sur ces types ; par exemple, ` (7 + 8) = 15 && false `. Une manière de les représenter en OCaml avec un type somme est la suivante : ```ocaml type expre = | Eq of expre * expre | Nbr of int | Bool of bool | Plus of expre * expre | Minus of expre * expre | Or of expre * expre | And of expre * expre ``` Le problème de cette représentation est que l'expression ```c 7 || (true + 4) ``` représentée dans ce type par ```ocaml Or (Nbr 7, Plus (Bool true, Nbr 4)) ``` est bien typée, car elle respecte la définition du type. Du point de vue du compilateur, tout va bien, même si sémantiquement, cette expression est absurde. La résolution classique de cette ambigüité passe par une redéfinition un peu bavarde du type `expre` ```ocaml type expre = | BoolExpre of bool_expre | IntExpre of int_expre and bool_expre = | BoolValue of bool | BoolEq of bool_expre * bool_expre | IntEq of int_expre * int_expre | BoolOr of bool_expre * bool_expre | BoolAnd of bool_expre * bool_expre and int_expre = | IntValue of int | IntPlus of int_expre * int_expre | IntMinux of int_expre * int_expre ``` Dans la pratique, on rencontre très fréquemment ce problème de typage dans la définition des types dérivés d'un type de base, par exemple un type "paramètre d'un programme" dont dérivent des types "callback lors de la mise à jour" ou "fonction de sérialisation du paramètre": l'ajout d'un cas dans le type de base demande l'ajout d'un ou plusieurs cas dans les types dérivés. ## Avec les GADT Les GADT permettent de redéfinir les expressions précédentes de sorte que les expressions correctement typées soient également sémantiquement valides. Les règles de validités sémantique, autrement dit l'"invariant", que l'on souhaite garantir, est donc exposé au compilateur, qui garantit que cet invariant est respecté. Redéfinissons notre type avec les GADT : ```ocaml type _ expre = | Eq : ('a expre * 'a expre ) -> bool expre | Nbr : int -> int expre | Bool : bool -> bool expre | Plus : (int expre * int expre) -> int expre | Minus : (int expre * int expre) -> int expre | Or : (bool expre * bool expre) -> bool expre | And : (bool expre * bool expre) -> bool expre ``` Si on compare cette définition à celle d'un type somme classique, une première différence saute aux yeux : les constructeurs (`Eq`, `Nbr`, ...) sont explicitement typés. Typer explicitement les constructeurs permet d'exprimer les relations de dépendances entre les constructeurs, le type de leurs arguments et le type résultant. Par exemple, le constructeur `Eq : 'a expre * 'a expre -> bool expre ` construit une expression booléenne à partir de deux expressions de même type : la sémantique de l'égalité est encodée directement dans le système de type. Le compilateur peut donc rejetter l'expression sémantiquement invalide ```ocaml # Or (Nbr 7, Plus (Bool true, Nbr 4));; Error: (Nbr 7) has type int expre but an expression was expected of type bool expre Type int is not compatible with type bool ``` On peut aussi utiliser ce type algébrique généralisé pour définir une fonction d'évaluation de ces expressions ```OCaml let rec eval : type etype. etype expre -> etype = function | Nbr n -> n | Bool b -> b | Eq (left, right) -> (eval left) = (eval right) | Plus (left, right) -> (eval left) + (eval right) | Minus (left, right) -> (eval left) - (eval right) | Or (left, right) -> (eval left) || (eval right) | And (left, right) -> (eval left) && (eval right) ``` Plusieurs choses intéressantes se voient dans cette fonction d'évaluation. Premièrement, le type de la fonction est défini explicitement, avec notamment la déclaration du type `etype`, que l'on "invente" pour l'occasion. Deuxièmement, le type de retour correspond au paramètre de type dans l'expression : il s'agit d'une fonction polymorphe, car la fonction renvoi le type "inventé". Mais si on regarde le type `etype` à l'intérieur du filtrage du motif, on se rend compte que ce type varie en fonction de la branche dans laquelle on se trouve. À l'intérieur de la branche `Nbr n` , `etype` est un entier `etype=int` tandis que dans la branche `Eq (left, right)`, `expr` est de type `etype=bool` alors que `left` et `right` sont de type ` 'a expre `. Les constructeurs des GADT imposent donc des contraintes de types différentes pour chaque branche. Cela permet d'exprimer des fonctions qui ont un comportement *et* un type différent pour chacun des constructeurs du GADT. On voit dans cet exemple que grâce aux GADT, la définition d'une fonction d'évaluation ne requiert pas l'introduction d'un type nouveau : on peut décrire la fonction d'évaluation et donc en particulier son type directement à partir du type de base. Avec l'ancienne définition du type `expre`, nous aurions du introduire un nouveau type somme décrivant le résultat d'une évaluation, et de surcroît, nous n'aurions pas pu représenter le fait que la fonction d'évaluation transforme une expression booléenne (resp. numérique) en valeur booléenne (resp. numérique) dans le système de types. Changements dans les bibliothèques standard =========================================== Chaînes de caractères immutables ------------------------------- En OCaml les chaînes de caractères de type `string` ont été historiquement conçues comme mutables. Cependant, au fil des années, le consensus a évolué pour considérer qu'il était généralement préférable d'utiliser ce type comme s'il était immutable. Cette différence entre la pratique et le système de type était une bizarrerie pour un langage fonctionnel qui se targue de l'expressivité de son système de types. Il a donc été décidé de séparer le type `string` et le module attenant `String` en deux : d'un côté les chaînes de caractères immutables `String`, d'un autre les tampons mutables `Bytes`. Néanmoins, une séparation brutale aurait cassé un trop grand nombre de bibliothèques tierces. Pour adoucir cette séparation, le compilateur considère par défaut que les modules `Bytes` et `String` et les types associés sont identiques. Pour activer la distinction entre ces modules, il est nécessaire de passer l'option `-safe-string` au compilateur durant la compilation. Usages des GADT dans la bibliothèque standard ---------------------------------------------- Les types algébriques généralisés étant un ajout récent dans le compilateur, ils ne sont pas encore très utilisés dans la bibliothèque standard d'OCaml. Cependant, dans cette nouvelle version, les GADT apparaissent dans deux composants importants d'OCaml : le module Printf et la bibliothèque Bigarray. ### La bibliothèque Bigarray La distribution standard du compilateur OCaml inclut une bibliothèque de manipulation des tableaux multidimensionnels nommée Bigarray. Conçue pour s'interfacer facilement avec le C et le Fortran, elle est basée sur un type relativement complexe : ` ('a, 'b, 'c) Genarray.t `. Les trois paramètres de type correspondent à : * ` 'a ` : le type de la représentation OCaml des éléments * ` 'b ` : le type de la représentation en C des éléments * ` 'c ` : l'ordre des dimensions dans le tableau (i.e. layout fortran ou C) Laissons de côté pour l'instant le type 'c. Si on regarde les types `'a` et `'b`, il est clair que les deux sont interdépendants. Pour gérer cette interdépendance, la bibliothèque Bigarray introduisait un type abstrait `('a, 'b) kind` qui représentait un couple valide de représentation OCaml et C. Les valeurs possibles de ce type étaient réalisées grâce à des variables opaques telles que : * ` float32 : (float, float32_elt) kind ` * ` int : (int, int_elt) kind` Le problème avec ces variables opaques était qu'il est totalement impossible de les manipuler, ce qui posait problème dans certaines situations. Pour pallier ce problème, le type abstrait `kind` a été remplacé par un GADT de la forme : ```OCaml type (_,_) kind = | Float32 : (float, float32_elt) kind | Int : (int, int_elt) kind | ... ``` L'avantage de cette construction est qu'elle permet d'exprimer les contraintes sur les types `'a` et `'b` directement dans la définition du type `kind`. De ce fait, il n'est plus nécessaire d'utiliser des variables opaques pour faire respecter l'interdépendance entre `'a` et `'b`. En utilisant les variables de type `kind`, il devient possible de définir une fonction renvoyant l'équivalent du zéro pour le type OCaml 'a ``` OCaml let zero (type a) (type b) ( k : (a,b) kind) : a = match k with | Float32 -> 0. (* float *) | Int -> 0 (* int *) | ... ``` L'utilisation des types algébriques généralisés permet donc d'avoir un comportement **et** un type spécifique pour chacune des branches du filtrage de motif. À partir de cette fonction `zero`, on peut ensuite écrire une fonction générique qui construit un tableau rempli de zéros ``` Ocaml let create_zero kind layout dims = let array = Genarray.create kind layout dims in (* uninitialized array *) Genarray.fill (zero kind); array ``` Une autre utilisation possible serait de calculer la taille en bytes d'un tableau sans avoir à passer par les détails de l'implémentation. ### Réécriture de la bibliothèque Printf Qu'est-ce que le module `Printf` ? Il s'agit tout simplement du module associé à la fonction `C` éponyme, mais à la sauce OCaml : avec l'inférence de type. Cela signifie que l'instruction suivante ne compilera pas : ```OCaml # Printf.printf "Hello %d" "world";; Error: This expression has type string but an expression was expected of type int ``` En effet, le paramètre `world` ne correspond pas au type `%d` attendu. Comment est-ce possible ? Jusqu'à présent grâce à une magie interne assez difficile à maintenir. Celle-ci a désormais été réécrite à l'aide de la syntaxe des GADT. Cela ne change pas grand-chose pour l'utilisateur (l'interface du module n'a pas changé), mais il s'agit d'une réécriture d'un module particulièrement compliqué, et un exemple de ce qu'il est possible de faire à travers les GADT. Que devient OCaml aujourd'hui ? =============================== La dernière dépêche remonte à maintenant deux ans, et était sortie pendant une transformation du langage : ces dernières années, OCaml est passé d'un langage universitaire, destiné à implémenter des algorithmes et enseigner l'informatique, à celui d'un langage professionnel qui se veut apte à répondre à des besoins actuels. Qu'en est-il aujourd'hui ? Nouveau wiki ------------ Un wiki est maintenant disponible à l'adresse suivante : http://ocaml.org/, le site se veut être la vitrine communautaire du langage. Opam ---- [Opam](http://opam.ocamlpro.com/) est le nouveau gestionnaire de paquets pour OCaml. À la manière de maven (pour java), ou pip (en python), il permet d'accéder à l'ensemble des bibliothèques disponibles à travers un seul outil. Il y a supplanté Godi qui n'avait pas la même visibilité. Signe des temps, opam a été développé par [ocamlpro](http://www.ocamlpro.com). Opam permet de maintenir plusieurs versions du langage sur un même poste, et de basculer d'une version à une autre. Créer un dépôt local est aussi relativement aisé et cela plus encore dans la future version 1.2 (actuellement en beta) qui permet d'installer un nouveau paquet à partir de n'importe quel dépôt git contenant les métadonnées requises. Accès au code via github ------------------------ La communauté s'est posée la question de savoir comment obtenir des retours de la part des contributeurs. La liste de diffusion est active, mais il s'agit d'un principe vieillissant, qui ne facilite pas les échanges de code. Le serveur hébergeant le code est le serveur de l'INRIA basé sur un svn. Il a été décidé expérimentalement d'ouvrir OCaml sur [github](https://github.com/ocaml/ocaml) pour voir si les retours seraient plus nombreux en terme de contributions (voir la [discussion](http://alan.petitepomme.net/cwn/2013.12.24.html#5) sur la liste de diffusion et [l'annonce](http://gallium.inria.fr/blog/patch-review-on-github) sur le blog de l'équipe Gallium). L'ensemble des branches de développement sont disponibles à travers opam, ce qui permet de basculer très facilement sur une version expérimentale pour tester les fonctionnalités à venir. Nombreuses bibliothèques pour la vraie vie ----------------------------------------- Lorsqu'on regarde l'ensemble des bibliothèques disponibles pour OCaml, nombre d'entre elles servent à des problématiques vraiment pratiques, ne couvrant pas forcément que des sujets théoriques ; citons par exemple : ### Pour le réseau - [Ocsigen](http://ocsigen.org/), un framework pour écrire très rapidement des applications web (client et serveur) entièrement en OCaml - [Js_of_ocaml](http://ocsigen.org/js_of_ocaml/) est un compilateur développé par le projet ocsigen traduisant le bytecode OCaml en code javascript - [Ocamlnet]( http://projects.camlcity.org/projects/ocamlnet.html), la bibliothèque couteau suisse pour de nombreux protocoles réseau. Cette bibliothèque énorme a été utilisée en production chez MyLife.com. Elle compte, entre autres, nethttpd, un serveur web haute performance, très scalable. - Un [binding SSL](https://github.com/savonet/ocaml-ssl) - Une implémentation de [TLS](https://opam.ocaml.org/packages/tls/tls.0.1.0/) - Une bibliothèque [pour le SMTP](http://www.linux-nantes.org/~fmonnier/OCaml/smtp-mail/) - Un [serveur WebSocket](https://github.com/mzp/websocket-ocaml) ### Bibliothèques côté clients - Macaque (écrit par Gasche), un [DSL au dessus de OCaml pour gérer des données sur PostgreSQL de manière typée](http://macaque.forge.ocamlcore.org/) - Un [client Redis](https://github.com/rgeoghegan/ocaml-redis) - Un [client Memcache](https://github.com/barko/kache). ### Format de fichiers - Un utilitaire pour écrire des [sérialiseurs/désérialiseurs JSON/OCaml](https://github.com/MyLifeLabs/atdgen) de manière transparente - [Camlpdf](https://github.com/johnwhitington/camlpdf) une bibliothèque pour manipuler la structure de fichiers pdfs. ### Autres projets intéressants - Une bibliothèque pour [utiliser la Graph API de Facebook](https://github.com/dominicjprice/facebook-sdk) - Un [ensemble d'outils pour réaliser de l'analyse de code](https://github.com/facebook/pfff) dans de nombreux langages - Un outil pour [manipuler les graphes RDF](http://ocaml-rdf.forge.ocamlcore.org/) - Une bibliothèque d'accès à [Neo4j](https://github.com/Kakadu/ocaml-neo4j) - LiquidSoap : un [outil complet](http://savonet.sourceforge.net/index.html) de traitement des flux audios avec de nombreuses possibilités d'entrée/sortie (ALSA, Icecast,...) On pourra trouver une liste assez exhaustive des bibliothèques disponibles sur [caml.org](https://opam.ocaml.org/packages/) Livres ------ Quelques livres sont sortis depuis l'année dernière sur le langage. Ils n'ont pas été présentés sur le site au moment de leur parution, voici donc l'occasion de les présenter pour ceux qui veulent s'intéresser au langage : [Real World OCaml](https://realworldocaml.org/), sorti en novembre 2013, présente le langage, mais se veut être un livre pratique pour répondre aux cas d'utilisation que l'on peut rencontrer au quotidien : comment écrire un [parseur syntaxique](https://realworldocaml.org/v1/en/html/parsing-with-ocamllex-and-menhir.html), la construction d'une interface graphique à travers les [mixins](https://realworldocaml.org/v1/en/html/classes.html#mixins)... Il a été écrit en partenariat avec les utilisateurs OCaml puisque tout le monde pouvait contribuer à sa rédaction, et est aujourd'hui entièrement disponible en ligne. [OCaml from the Very Beginning](http://ocaml-book.com/) permet de se plonger dans le langage en douceur. Il est destiné à un public ne connaissant pas le langage et souhaitant le découvrir. [More OCaml : Algorithms, Methods & Diversions, tout récemment](http://ocaml-book.com/more-ocaml-algorithms-methods-diversions), complète le précédent et permet d'aller plus loin dans le langage. Remerciements ============= L'écriture de cette dépêche n'aurait pas été possible sans la participation de tous. Merci à BAud, chimrod, Def, Jacques-Pascal Deplaix, jumbojet, Michaël, octachron, Ontologia, palm123, Snark et Tonton Th pour leur remarques et le temps passé à l'écriture.