Greboca  

Suport technique et veille technologique

Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.

C’est alors la barrière de la prise en main qui fait peur, et pourtant...

Les logiciels libres

L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.

Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.

Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.

Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.

DLFP - Dépêches  -  OCaml en 2021

 -  Septembre 2021 - 

La version 4.13.0 du langage OCaml est sortie le 24 septembre 2021, sept mois après OCaml 4.12.0 sortie le 24 février 2021.

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. La plus grande spécificité d’OCaml dans le paysage des langages fonctionnels (Haskell, Rust, F#…) est probablement son système de module : les modules d’OCaml font partie intégrante du langage, et il est par exemple possible de décrire des modules paramétrés par d’autres modules (à travers des foncteurs).

La grande nouveauté de cette année 2021 est la convergence de l’environnement d’exécution entre la version standard d’OCaml et le prototype d’OCaml multi-cœur. Cette convergence amorce une nouvelle étape dans la transition vers OCaml multi-cœur. Au-delà des progrès vers OCaml multi-cœur, cette année 2021 a vu une de nombreuses avancées pour le langage OCaml et son compilateur que ce soit en termes d’architectures supportées, de messages d’erreurs, de fonctionnalités du système de types, mais aussi des améliorations de confort pour les programmeurs que ce soit au niveau des outils de profilage, de la gestion des avertissements ou de la bibliothèque standard.

Sommaire

La route vers le multi-cœur et OCaml 5.0

Une des limites de l’implémentation actuelle de l’environnement d’exécution d’OCaml est son utilisation d’un verrou global. Ce verrou empêche les applications multithreads de bénéficier du parallélisme des fils d’exécution (threads). Au cours du temps, il y a eu plusieurs tentatives d’enlever ce verrou. La dernière initiative a germé chez OCaml Labs vers 2014-2015. Pour éviter les échecs précédents, cette initiative a décidé de se concentrer sur deux points : une compatibilité descendante presque parfaite avec la version monocœur d’OCaml, et une intégration incrémentale dans la branche principale d’OCaml. Ce travail de fond a commencé à être visible dans OCaml 4.10.0. Mais il s’est notablement accéléré dans OCaml 4.12.0. Une grande partie du travail dans OCaml 4.12 et 4.13 a été consacrée à diminuer les divergences entre l’environnement d’OCaml multi-cœur et la version principale d’OCaml.

Par exemple, un des changements majeurs prévus pour OCaml multi-cœur est la gestion des pointeurs pointant en dehors de la mémoire gérée par OCaml, sans être gardés par des métadonnées (parce que, par exemple, ils ont été alloués par une bibliothèque C externe). Dans la version monocœur d’OCaml, ces pointeurs étaient gérés en gardant une trace des zones mémoires allouées par OCaml. En passant à un environnement d’exécution multi-cœur, cette stratégie devient prohibitive en coût de synchronisation. Ces pointeurs nus ne seront donc pas autorisés dans OCaml multi-cœur. Pour assurer une évolution en douceur, OCaml 4.12.0 a ajouté deux options de configuration : une option pour désactiver la gestion des pointeurs nus directement pour les audacieux ; et une version plus prudente qui rajoute un test dynamique de la présence de ces pointeurs nus. Cette dernière option est notamment utilisée pour tester toutes les bibliothèques et programmes disponibles sur Opam (le dépôt de paquets d’OCaml).

Un autre point important est la gestion de l’ordonnancement entre l’utilisateur et l’environnement d’exécution (runtime). Dans la version monocœur d’OCaml, l’environnement d’exécution reprend la main à chaque allocation. Cela lui donne l’occasion de vérifier si le Glaneur de Cellules (GC) à du travail à faire, ou s’il faut s’occuper de signaux en attente. Une conséquence est qu’il est possible d’écrire du code numérique qui n’alloue jamais et ne rend jamais la main à l’environnement d’exécution. En absence de parallélisme, ce comportement est plus une curiosité qu’autre chose. Mais pour multi-cœur OCaml, ce comportement égoïste n’est plus de mise. Dans sa conception actuelle, OCaml multi-cœur a une phase de GC en parallèle, pendant laquelle tous les fils d’exécution exécutent une passe de GC de manière synchrone. Il n’est donc pas question qu’un fil d’exécution bloque le GC de tous les autres fils. Le compilateur natif a donc été modifié dans OCaml 4.13.0 pour s’assurer qu’un fil d’exécution passe toujours la main à l’environnement d’exécution dans un temps borné.

Un élément qui commence à apparaître dans les discussions sur OCaml multi-cœur est que l’on se rapproche d’un point où il ne reste plus qu’à faire le grand saut et intégrer le runtime multi-cœur, et absorber les petites pertes de performances inévitables pour le code séquentiel.

La première version d’OCaml qui intégrera la prise en charge du multi-cœur sera OCaml 5.0. Cette nouvelle majeure commencera avec une période de transition durant laquelle la branche 4 sera maintenue activement.

Cette première version d’OCaml multi-cœur n’intègrera pas la partie la plus innovante de la proposition initiale, le système d’effet, et se contentera d’exposer une bibliothèque de domaines et quelques API de plus haut niveau, bâtis au-dessus de cette bibliothèque de domaine.

Le but est de découpler la partie runtime du développement d’OCaml multi-cœur du travail de conception sur le système d’effet qui requiert encore des efforts de conception.

Une prise en charge étendue de RISC-V à macOS/ARM64

Le compilateur OCaml gère deux modes de compilation : un mode bytecode qui fonctionne sur toute architecture où un compilateur C est disponible ; et un mode natif qui émet directement des binaires natifs. Ce mode natif est d’ailleurs le seul utilisateur du système objet d’OCaml au sein du compilateur lui-même.

Cette gestion native requiert de s’adapter aux nouvelles familles de processeurs et aux variations d’ABI suivant les systèmes d’exploitation. OCaml 4.11.0 a ainsi vu apparaître la prise en charge du RISC-V sous Linux. De manière similaire, la prise en charge pré-existante pour ARM64 a été étendue pour couvrir les conventions d’appels de macOS dans OCaml 4.12.0 .

De meilleurs messages d’erreurs

Écrire des messages d’erreurs utiles est une tâche plus difficile qu’il n’y paraît. Il peut être tentant de communiquer une erreur interne sur l’implémentation ou d’évoquer une théorie avec laquelle l’utilisateur n’est pas familier. Un autre problème assez fréquent pour les erreurs de types dans OCaml est que le vérificateur de type est optimisé pour vérifier rapidement que le code est bien typé. Avec ce mode de fonctionnement, on ne découvre parfois une erreur uniquement après qu’une série de petites erreurs nous ait mené à une situation impossible.

En bref, il reste pas mal de travail à faire pour améliorer les messages d’erreurs d’OCaml. Mais cette année 2021 a vu quelques progrès intéressants, et d’autres sont déjà intégrés ou en cours d’intégration dans la version de développement d’OCaml.

Des messages d’erreurs plus détaillés pour les foncteurs

Les foncteurs sont des fonctionnalités uniques d’OCaml. Ils permettent de décrire des modules qui dépendent d’autres modules. Par exemple, la définition d’un module Graphe peut prendre comme argument un module Sommet et un module Arete :

module Graphe(Sommet:SOMMET)(Arete:ARETE) = struct ... end

Je peux ensuite instancier ce foncteur avec diverses implémentations de ARETE et SOMMET.

Par exemple :

module Graphe_basique = Graphe(Sommet_basique)(Arete_basique)
module Graphe_colore = Graphe(Sommet_colore)(Arete_basique)

Cette formulation en termes de foncteur permet de décrire des algorithmes de graphes indépendamment de l’implémentation des arêtes ou sommets (sont-ils nommés ? colorés ?).

Avant OCaml 4.13, les erreurs liées à ces foncteurs pouvaient être très verbeuses. Par exemple, si j’applique le foncteur Graphe avec un argument en trop :

module G = Graphe(Etiquette)(Sommet)(Arete)

Le vérificateur de type d’OCaml se plaignait que le module Etiquette n’est pas un SOMMET, ce qui donne un message d’erreur qui ressemble à cela :

       Modules do not match:
         sig type t = string end
       is not included in
         SOMMET
       The value `label' is required but not provided
       The value `create' is required but not provided
       The type `label' is required but not provided
       The value `equal' is required but not provided
       The value `hash' is required but not provided
       The value `compare' is required but not provided

Avec OCaml 4.13.0, le vérificateur de type prend de la hauteur et essaye d’identifier des erreurs de haut niveau dans les erreurs liées aux foncteurs : est-ce que l’utilisateur n’aurait pas oublié un argument ? Ajouté un argument ? Modifié quelques arguments ?

       Error: The functor application is ill-typed.
       These arguments:
         Etiquette Sommet Arete
       do not match these parameters:
         functor (Sommet : SOMMET)(Arete : ARETE)} -> 
      1. The following extra argument is provided
        Etiquette : sig type t = string end
      2. Module Sommet matches the expected module type SOMMET
      3. Module Arete matches the expected module type ARETE

De plus en utilisant des méthodes de diffing (comparaison, généralement utilisées dans les correcteurs orthographiques ou du fuzzy searching/recherche floue), le vérificateur de type est capable de trouver une erreur la plus probable même dans des cas complexes.

Confusion entre module et module types

Un des détails surprenants d’OCaml est que beaucoup d’objets ont leur espace de noms séparé, ce qui mène parfois à des erreurs entêtantes. Par exemple :

module type M = sig type t end
type u = M.t

Error: Unbound module M

ce message en OCaml 4.10.0 semble s’obstiner à ne pas reconnaître l’existence de M.

Le véritable problème est que M n’est pas un module, et donc ne définit pas de types. Depuis la version 4.12.0, le message d’erreur reconnaît qu’il s’agit d’une confusion naturelle :

Error: Unbound module M
Hint: There is a module type named M, but module types are not modules

Une explication des problèmes de régularité

Parfois, les messages d’erreurs sont évidents pour leurs auteurs, et totalement obscurs sans le bon contexte.

C’était notamment le cas d’un des messages d’erreurs concernant les types récursifs non-réguliers. Si l’enchaînement des mots précédents ne vous parle pas, il y avait de grandes chances que ce message d’erreur vous laisse pantois :

   type ('a,'b) x = [ `X of ('b,'a) y ]
   and ('a,'b) y = [ `Y of ('a,'b) x ]

Error: In the definition of y, type ('b, 'a) x should be ('a, 'b) x

Il commet en effet trois péchés cardinaux pour un message d’erreur : il propose un correctif faux, il ne parle pas du code visible par l’utilisateur mais du résultat d’un calcul invisible du compilateur, et il ne pointe pas vers la source de l’erreur.

Ce souci est corrigé, et OCaml 4.12.0 prend désormais le temps d’expliquer le problème :

Error: This recursive type is not regular.
The type constructor x is defined as
type ('a, 'b) x
but it is used as
('b, 'a) x
after the following expansion(s):
('b, 'a) y = [ `Y of ('b, 'a) x ]
All uses need to match the definition for the recursive type to be regular.

Le message d’erreur est long. Cependant il explique non seulement la nature du problème (un type paramétré est utilisé de façon différente au sein d’un même groupe de définition récursif) mais aussi comment le vérificateur de type a découvert l’erreur.

Améliorations de l’expérience utilisateur

Il y a aussi beaucoup d’améliorations de taille plus modeste qui sont plus difficiles à catégoriser.
Parmi celles qui ont retenu mon attention sur ces deux dernières versions, je peux citer :

Statmemprof : profiler la mémoire sur des programmes en production.

Pour des langages à glaneur de cellules (GC) comme OCaml, l’allocation et la désallocation de mémoire est un axe à la fois important et assez invisible de la performance des programmes. Il peut donc être important de surveiller le travail du GC dans un programme pour évaluer des problèmes de performances, ou s’assurer qu’il n’y ait pas de fuite de mémoire dans un serveur tournant durant des années.

Dans les versions d’OCaml antérieures à 4.12, la bibliothèque Spacetime fournissait de tels outils de surveillance en continu de la mémoire.

Cependant analyser le travail du GC peut-être extrêmement coûteux aussi bien en termes de temps que d’espace. Et il était pratiquement impossible d’utiliser Spacetime dans un environnement de production à cause de ces coûts.

Statmemprof est une réponse à ces problématiques : il s’agit d’un outil de profilage statistique de l’allocation et de la désallocation de la mémoire. En s’autorisant à n’analyser qu’une partie des allocations et des désallocations, il devient possible de contrôler le coût de cette analyse de la mémoire et de la rendre négligeable. Intégrer cette analyse dans du code en production devient alors possible. On peut même s’autoriser à ajuster le comportement du programme en fonction de sa consommation mémoire actuelle.

Des noms pour les warnings

Après 25 ans d’existence, OCaml a accumulé plusieurs dizaines d’avertissements (70 dans la version 4.13.0). Fort heureusement, la configuration de ces avertissements est souvent laissée soit au compilateur soit au système d’assemblage. Notamment, dune, le système d’assemblage de prédilection de la plupart des paquets opam, a un choix d’avertissements assez strict par défaut.

Il reste néanmoins pratique de pouvoir modifier cette configuration pour un fichier ou une fonction spécifique. Par exemple, on peut activer le warning 27 pour juste la fonction f avec :

let f x = () [@@warning "+27"]

Cependant, à la lecture, il n’est pas exactement évident de se rappeler l’objet de cet avertissement 27. Cela d’autant plus lorsque l’avertissement est utilisé ponctuellement. La nouvelle mouture d’OCaml permet enfin de nommer ces avertissements :

let f x = () [@@warning "+unused-var-strict"]

Et la Stdlib s’agrandit

La bibliothèque standard voit arriver deux nouveaux modules liés aux threads :

  • Atomic : ce module est là pour préparer en douceur la compatibilité avec le runtime multi-cœur.

    • Thread.Semaphore : ce module offre une contrepartie au Mutex qui n’a pas besoin d’être verrouillé et déverrouillé dans le même fil d’exécution.

et un nouveau module de structure de données :

  • Either : il s’agit d’un module d’alternative générique (on a soit un Left a soit un Right b) qui est utile lorsque nommer explicitement les deux alternatives serait pénible.

Fut un temps, la bibliothèque standard d’OCaml avait pour objectif de rester assez minimaliste. Ce choix a engendré la création d’au moins quatre bibliothèques étendant la bibliothèque standard (extlib, batteries, base, containers). Cependant depuis, OCaml 4.07 la bibliothèque standard s’est ouverte à plus d’améliorations. Néanmoins, l’évolution de la bibliothèque standard reste basée sur un principe de quasi-unanimité, son rythme d’évolution reste donc très mesuré.

Des piles d’appels plus expressives

Lorsque qu’une fonction lève une exception qui n’est pas attrapée, la pile d’appel (backtrace) contient désormais des informations sur les noms des fonctions qui se sont retrouvées sur la pile d’appel. Par exemple exécuter :

   let () =
     let f () =
       let g () = raise Exit in
       fun () -> g ()
      in
      f () ()

nous informe que

Raised at Backtrace_example.f.g in file "backtrace_example.ml" (inlined), line 3, characters 16-26

plutôt que le laconique

Raised at file "backtrace_example.ml" (inlined), line 3, characters 16-26

Plus de types pour les utilisateurs experts

Le système s’est aussi enrichi de fonctionnalités plus orientées vers les auteurs de bibliothèques, et les utilisateurs experts.

Des noms pour les types existentiels

Les types existentiels sont une des fonctionnalités nouvelles apportées par les Types de Données Algébriques Généralisés (GADTs). Pour faire simple, il s’agit de types qui n’existent qu’à l’intérieur d’un constructeur.

Par exemple, je peux décrire une pipeline de transformation de 'a vers b en plusieurs étapes :

type ('entree,'sortie) pipeline =
  | Vide: ('entree,'entree) pipeline
  | Etape: ('entree,'intermediaire) pipeline * ('intermediaire -> 'sortie) 
             -> ('entree,'sortie) pipeline

Ici le constructeur Etape prend comme argument un pipeline de entree vers un type intermediaire, et une fonction de ce type intermediaire vers le type sortie et me donne en retour un pipeline de l’entrée vers la sortie.

Le point intéressant avec définition est que ce type intermédiaire n’est pas un type concret connu. Il s’agit d’un type inconnu dont je sais seulement qu’il est partagé par ma pipeline interne, et ma fonction de transformation.

Une bonne façon de voir comment ce type se comporte est d’implémenter une fonction envoyer qui applique toutes les étapes de la pipeline à une entrée et obtient une sortie.

 let rec envoyer: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape(pipeline_interne, transformation_finale) ->
    entree |> envoyer pipeline_interne |> transformation_finale
    (* [x |> f] signifie [f x] *)

Ici, tout ce passe bien. Mais que se passe-t-il si j’essaye d’appliquer la transformation finale avant le reste de la pipeline ?

let rec envoyer_erronee: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape(pipeline_interne, transformation_finale) ->
    entree |> transformation_finale |> envoyer pipeline_interne

J’obtiens une erreur de compilation qui se plaint que le type de entree n’est pas le bon :

Error: This expression has type entree but an expression was expected of type
$Etape_'intermediaire

Et en effet, le code est faux parce que le type entree ne correspond pas au type attendu par la transformation finale. Le nom du type attendu $Etape_'intermediaire est cependant assez complexe.

Il s’agit d’un nom automatiquement généré pour un type existentiel à partir de la définition de type et du constructeur qui l’a introduit. Ici le nom est assez clair, mais dans des cas complexes ces noms générés automatiquement peuvent être difficiles à déchiffrer. Une des nouveautés dans 4.13.0 est qu’il est désormais possible de nommer soi-même les types existentiels introduits dans le filtrage de motif:

let rec envoyer_erronee: type entree sortie. (entree,sortie) pipeline -> entree -> sortie =
  fun pipeline entree ->
  match pipeline with
  | Vide -> entree
  | Etape (type intermediaire)
      (pipeline_interne, transformation_finale:
        (entree, intermediaire) pipeline * (intermediaire -> sortie)
      ) ->
    entree |> transformation_finale |> envoyer pipeline_interne

Cette fois-ci, le message d’erreur utilise notre nom de type :

Error: This expression has type entree but an expression was expected of type
intermediaire

Ce qui devrait réduire légèrement le temps passé à faire compiler du code utilisant fortement les GADT. Cette notation permet aussi d’obtenir facilement le type abstrait correspondant au type existentiel pour lequel il y a des applications plus élaborées.

De l’injectivité pour vos types

Les bibliothèques vont pouvoir ajouter des points d’exclamation à leurs types

type !'a vec

pour indiquer que le paramètre 'a est vraiment utilisé dans le type et n’est pas un type fantôme.
Cela permet de débloquer certains usages avancés des GADT où il est vital de savoir si int vec et forcément différent de float vec.

Par exemple, avec cette annotation, le vérificateur de type sait qu’avec la définition suivante :

type _ int_or_float_vec =
| Int_vec : int vec -> int vec int_or_float_vec
| Float_vec: float vec -> float vec int_or_float_vec

lorsqu’on a une valeur de type 'a int_or_float_vec, la variable 'a est forcément soit int vec soit float vec. En d’autres mots, on ne peut jamais se procurer une valeur de type char int_or_float_vec :

let impossible: char int_or_float_vec -> _ = function _ -> .

Sans cette annotation, le vérificateur de type ne peut éliminer la possibilité que le type 'a vec ait été défini en tant que synonyme de char:

type 'a vec = char

Comme pour les annotations de variances, l’injectivité est automatiquement inférée pour les types non-abstraits. Ces annotations sont donc essentiellement là pour les auteurs de bibliothèque.

Au-delà d’OCaml multi-cœur

Si l’implémentation d’OCaml multi-cœur se rapproche lentement mais inexorablement, les plans pour le futur d’OCaml ne s’arrêtent pas là.

En particulier, la gestion de la concurrence et du parallélisme sera à terme basée sur un système d’effets. La prochaine étape de ce côté sera de concevoir et déployer un système d’effets typés facile à utiliser en pratique.

Mais le développement d’OCaml 5 ne se concentrera pas uniquement sur l’aspect multi-cœur. Une des forces d’OCaml est son système de modules à la fois expressif et adapté à la compilation séparée. Cependant, cette puissance a un prix, et les usages avancés du système de modules peuvent être particulièrement lourds syntaxiquement. Un des projets en cours pour OCaml 5 est d’introduire des méthodes plus légères pour décrire des fonctions paramétrées par des modules, à travers un système de foncteurs légers et implicites.

Commentaires : voir le flux Atom ouvrir dans le navigateur

par octachron, Yves Bourguignon, chimrod, Ysabeau, Snark, Benoît Sibaud, yPhil, Bruno Ethvignot, Quidam, tisaac, dourouc05, syntaxerror, Michaël

DLFP - Dépêches

LinuxFr.org

Entretien avec GValiente à propos de Butano

 -  16 avril - 

GValiente développe un SDK pour créer des jeux pour la console Game Boy Advance : Butano.Cet entretien revient sur son parcours et les raisons (...)


Nouveautés d'avril 2024 de la communauté Scenari

 -  11 avril - 

Scenari est un ensemble de logiciels open source dédiés à la production collaborative, publication et diffusion de documents multi-support. Vous (...)


Annuaire de projets libres (mais pas de logiciels)

 -  9 avril - 

Les communs sont une source énorme de partage !S’il est plutôt facile dans le monde francophone de trouver des ressources logicielles (Merci (...)


Les enchères en temps réel, un danger pour la vie privée mais aussi pour la sécurité européenne

 -  7 avril - 

Les enchères en temps réel, ou Real-Time Bidding (RTB), sont une technologie publicitaire omniprésente sur les sites web et applications mobiles (...)


XZ et liblzma: Faille de sécurité volontairement introduite depuis au moins deux mois

 -  31 mars - 

Andres Freund, un développeur Postgres, s’est rendu compte dans les derniers jours que xz et liblzma ont été corrompus par l’un des mainteneurs du (...)