OCaml 4.06 et 4.07

Un résumé des nouveautés dans les dernières versions d'OCaml

La version 4.07.0 du langage OCaml a été publiée le 10 juillet 2018 soit quelques mois après la sortie de la version 4.06.0, annoncée le 3 novembre 2017. OCaml est un langage fonctionnel de la famille des langages ML (dont font partie SML et F#). Il s’agit d’un langage fonctionnel multi‐paradigme fortement typé qui permet de mélanger librement les trois paradigmes : fonctionnel, impératif et objet.

OCaml arrive en version 4.07 avec un tout nouvel espace de noms, Stdlib, pour sa bibliothèque standard. Ce nouvel espace de noms présage l’intégration progressive de nouveaux modules dans la bibliothèque standard.

Un autre changement majeur, OCaml 4.06 marque la fin de la transition vers des chaînes de caractères immuables, changement amorcé dès OCaml 4.02 .

À côté de ces changements majeurs, on retrouve de nombreuses améliorations de qualité de vie : de nouveaux opérateurs d’indexation et des champs hérités pour les types objets. Mais aussi pas mal de travail de fond pour préparer l’intégration de la branche multicore, améliorer les passes d’optimisations Flambda, ou faire évoluer le système de types en fixant des irrégularités.

Bibliothèque standard

Une des nouveautés majeures d’OCaml 4.07 est la migration de la bibliothèque standard vers un espace de noms propre Stdlib. Cette migration a pour principal objectif de pouvoir ajouter de nouveaux modules à la bibliothèque standard sans casser les programmes tiers.

Désormais, les modules de la bibliothèque standard sont définis au sein du module Stdlib, module qui est ouvert par défaut par le compilateur. Ainsi, le module List est, par défaut, un raccourci pour Stdlib.List. Néanmoins, il est désormais possible de créer un module List sans craindre d’écraser le module Stdlib.List en aval :

let trois = List.length [1;2;3]
(* est désormais un raccourci pour *)
let trois = Stdlib.List.length [1;2;3]
(* ce qui est permet aussi d'écrire *)
module List = struct ... end
let trois = Stdlib.List.length [1;2;3]

Cette solution a d’ores et déjà permis d’ajouter deux nouveaux modules à la bibliothèque standard Seq et Float. Le nouveau module Seq définit un nouveau type de donnée pour des itérateurs externes, tandis que Float regroupe les constantes réelles et les fonctions opérant sur les Float. De manière similaire, la bibliothèque Bigarray fait désormais partie de la bibliothèque standard.

Le dernier changement majeur est le basculement vers des chaînes de caractères immuables (immutable) par défaut dans OCaml 4.06. Ce changement avait été amorcé dans OCaml 4.02, avec une dépréciation des fonctions manipulant le type string de manière mutable et une option de configuration renforçant le caractère immuable. Cette dernière option est désormais activée par défaut.

Nouveautés dans le langage

Opérateurs d’indexation

Après une période d’incubation, il est désormais possible de définir ses propres opérateurs d’indexation en dehors des types array, string et bigarray. C’est particulièrement utile pour manipuler des dictionnaires :

module Dict = struct
  include Map.Make(String) (* importation d'un module `Map`classique *)
  let (.?()) dict clef = find_opt clef dict
end
open Dict

let dict = Dict.of_seq (List.to_seq ["one", 1; "dos", 2; "drei", 3])
let trois = dict.?("drei")
(* ou *)
let trois = dict.Dict.?("drei")

Ou pour définir des formes de tableaux spécialisés sans perdre la syntaxe pratique des tableaux généralistes.

Pour bien marquer la différence entre ces nouveaux opérateurs d’indexation et les opérateurs d’indexation de base, leur nom doit comporter au moins un symbole supplémentaire entre le point . et la parenthèse ouvrante (.

Cependant, la syntaxe a encore besoin d’un peu de rodage pour être vraiment utilisable pour les tableaux multidimensionels des librairies de calcul numérique comme owl.

Au monde des objets

Le système objet d’OCaml a un champ d’application moins vaste que dans les langages orientés objet comme C++ ou Java. En partie parce que le système de modules répond à un grand nombre des questions de modularité et d’encapsulations qui sont le domaine des objets dans un langage purement objet. Les objets n’en demeurent pas moins utiles, et OCaml 4.06 apporte une nouvelle option pour composer plus facilement des types objets : les champs hérités. Par exemple, on peut partir d’un type animal :

type animal = < respire: unit >

Ce qui définit le type d’un objet doté d’une méthode respire sans argument. On peut ensuite définir un type mobile :

type mobile = < avance: int >

Puis combiner les deux :

type animal_mobile = < animal; mobile >

Il était déjà possible d’arriver à ce résultat en jonglant avec les types de classes, mais cette nouvelle méthode est bien plus intuitive.

Ce genre de code met en exergue une des particularités du système objet d’OCaml, qui est structurel : un objet n’est défini que par les méthodes qu’on peut lui envoyer et non par sa classe, ce qui donne au final un système qui s’apparente à une version statique du duck‐typing à la Python.

Meilleure intégration des types algébriques généralisés

Un des travaux de fond dans OCaml 4.07 a été l’amélioration du traitement des types algébriques généralisés (ou GADT) au sein du vérificateur de types.

Lors de l’introduction des GADT dans OCaml 4.00, ceux‐ci se sont souvent vus octroyés des chemins d’exécution particuliers pour séparer cette nouvelle extension du cœur mieux testé du langage. Après sept versions, les codes côté GADT et côté classique ont été unifiés. D’un point de vue utilisateur, cela signifie surtout qu’il n’est plus nécessaire de qualifier les GADT lorsque l’on filtre un schéma avec match :

module M = struct
  type en_attente =
    | Fini : en_attente
    | En_cours : 'a * ('a -> en_attente) -> en_attente
end
let execute (x : M.en_attente) = match x with
  | Fini -> M.Fini
  | En_cours (x,f) -> f x

Alors qu’il fallait précédemment qualifier les branches du match :

let execute (x : M.en_attente) = match x with
  | M.Fini -> M.Fini
  | M.En_cours (x,f) -> f x

Une des nouveautés, qui concerne plus les usages avancés, est l’apparition de variants vides, c’est‐à‐dire de types de variants sans aucune valeur associée :

type unique = |

Ce type étrange est principalement utile pour créer de manière explicite un nouveau type unique, qui ne sera utilisé que dans le système de type ou dans la génération de code.

Du côté des modules

Pour les utilisateurs avancés, il est désormais plus facile de préserver les alias de modules, que ce soit avec module type of ou des contraintes with modules. Par exemple, avec :

module A = struct type t end
module B = struct module Alias = A end

module type S = module type of B

S est désormais équivalent à :

module type S' = sig module Alias = A end

plutôt que :

module type S_sans_alias = sig
  module Alias: sig type t end
end

L’ancien comportement peut être rétabli en ajoutant un attribut [@remove_aliases] :

module type S_sans_alias = module type of S [@remove_aliases]

Un autre changement est qu’il est désormais possible d’utiliser des substitutions destructives à l’intérieur de sous‐modules :

module type S = sig
  module Inner: sig
    type t
    val x: t
  end
end
module type S' = S with type Inner.t := int

Messages d'erreur

Les messages d’erreur émis par OCaml ne sont pas toujours très clairs. Des efforts sont en cours pour corriger ce point. Par exemple, OCaml 4.07 essaie d’expliquer plus en détails certaines erreurs courantes chez les débutants, par exemple en cas d’oubli d’un argument () :

let un () = 1 
let test = (0 = un);;

Error: This expression has type unit -> int but an expression was expected of type int Hint: Did you forget to provide `()' as argument?

Le contexte de certaines erreurs est désormais mieux détaillé :

let () = if () then ()

Error: This variant expression is expected to have type bool because it is in the condition of an if-statement

plutôt que juste :

Error: This variant expression is expected to have type bool

Les types faiblement polymorphiques, qui auparavant étaient marqués par juste un tiret _, ont maintenant des noms plus explicites :

let none = ref None

none: '_weak1 option ref

Cela dans l’espoir de les rendre plus apparents et facilement cherchables, notamment dans le manuel.

Enfin, pour les utilisateurs plus avancés, les messages d’erreurs concernant les foncteurs et modules sont passés de :

module F() = struct end 
let x = F.x

Error: The module F is a functor, not a structure

à une version qui explique pourquoi l’extrait de code plus haut est invalide :

Error: The module F is a functor, it cannot have any components

Documentation

Le manuel de référence a fait peau neuve pour la version 4.07. L’apparence graphique du manuel commençait à faire un peu daté, et un rafraîchissement de façade était de rigueur.

Sur le fond, le manuel s’est enrichi d’un nouveau chapitre sur les troubles liés au polymorphisme, que ce soit les types faiblement polymorphiques :

let nouvel_identite () = fun x -> x
let id = nouvel_identite ()
let erreur = id id

Le polymorphisme d’ordre supérieur :

let f identite = identite 1, identite 2.

ou les fonctions polymorphiquement récursives :

let rec etrange l = match l with 
| [] -> 0
| [ _ ] -> 1
| a :: q ->  etrange [q];;

6 commentaires

Je suis assez impressionné par le nombre de changements qu’il y a dans une si petite version O_o

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte