OCaml 4.06 et 4.07

Posté par  . Édité par ZeroHeure, Snark, Julien Jorge, Davy Defaud, Pierre Jarillon, Nils Ratusznik et j_m. Modéré par Pierre Jarillon. Licence CC By‑SA.
Étiquettes :
37
2
oct.
2018
Programmation fonctionnelle

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.

Logo OCaml

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.

Sommaire

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];;

Aller plus loin

  • # On peut mélanger les trois paradigmes mais...

    Posté par  . Évalué à 3.

    Des objets dans du code OCaml, c'est un peu comme une guitare électrique au milieu d'un orchestre symphonique. ;-)

    Beaucoup de fonctionnel pur, un peu d’impératif, c'est ça du code OCaml classe et efficace.

    Notez qu'en OCaml, on peut donc avoir la classe, même sans objets! ;-)

    PS: ca se fait le coup de la guitare au milieu de l'orchestre,
    cf. Ennio Morricone, mais tout le monde n'est pas Ennio Morricone
    donc abstenez-vous.

    • [^] # Re: On peut mélanger les trois paradigmes mais...

      Posté par  . Évalué à 1.

      Il est vrai que les débutants en OCaml venant de langages objets ont tendance à abuser des objets, et il est tout à fait possible d'écrire du OCaml sans jamais toucher le système objet.

      Néanmoins, il ne faut pas non plus exagérer, dans certaines situations, les objets sont la solution la plus naturelle et élégante. Par exemple, les objets sont pratiques pour implémenter de la récursion ouverte sans passer par des records de fonctions avec un argument self explicite.
      Un bon exemple est la bibliothèque visitors. Similairement, les objets sont pratiques dans les bibliothèques d'interfaces comme lambda-term ou lablgtk pour pouvoir réutiliser des jeux de widgets.

      • [^] # Re: On peut mélanger les trois paradigmes mais...

        Posté par  (site web personnel) . Évalué à 3.

        Les widgets sont toujours présenté comme le moyen le plus évident de faire de l'objet. Pourtant, dans java FX pour éviter les arborescences horribles d'héritages, tous les objets sont des "nodes", et l'héritage est plat.

        Si on regarde la technique du DOM virtuel (ELM, react,…), il s'agit toujours de créer un arbre d'objet avant de faire un diff avec l'existant avant de l'afficher. Ocaml gère très bien des arbres qui ne sont pas des objets au sens strict.

        "La première sécurité est la liberté"

  • # optimisation et propagation de constante

    Posté par  (site web personnel) . Évalué à 3.

    Les templates de C++ sont connu pour pouvoir à la fois générer du code à la compilation, ce qui permet d'avoir du code très performant, et d'être parfaitement illisible en tant que langage dans le langage un peu pourris.

    Je me demandais à quel point la propagation de constante pouvait remplacer les templates, voir même la génération de code. En général, la propagation de constante se limite aux littéraux( 10, 'c'). Pourquoi ne pas aller au delà, et faire de la propagation de constante de conteneur ?

    Imaginez la propagation d'une string qui définit une expression régulière : cela revient à générer le code lié à cette expression.

    On peut imaginer la même chose avec une structure arborescente : un interpréteur d'AST classique devient un générateur de code sur un AST statique.

    Qu'en pensez-vous ?

    "La première sécurité est la liberté"

    • [^] # Re: optimisation et propagation de constante

      Posté par  . Évalué à 1.

      Pour faire de la meta programmation en OCaml, il y a MetaOCaml.
      Cf. http://okmij.org/ftp/ML/MetaOCaml.html

      • [^] # Re: optimisation et propagation de constante

        Posté par  (site web personnel) . Évalué à 3.

        Magnifique…

        let rec spower n x =
        if n = 0 then .<1>.
        else if n mod 2 = 0 then .<square .~(spower (n/2) x)>.
        else .<.~x * .~(spower (n-1) x)>.;;
        (* val spower : int -> int code -> int code = <fun> *)


        let spower7_code = .< fun x -> .~(spower 7 .< x >.)>.;;

        Je trouve cela particulièrement illisible. Et avec de la bête propagation de constante on aurait
        let spower7_code = spower 7

        "La première sécurité est la liberté"

        • [^] # Re: optimisation et propagation de constante

          Posté par  (site web personnel) . Évalué à 2.

          J'ai l'impression que ce que tu demandes est déjà disponible via un système d'optimisation de code nommé f-lambda. Une fois activé, cela parcours tout le code pour rechercher les optimisations possibles au niveau des appels de fonction, les inliner, etc

          • [^] # Re: optimisation et propagation de constante

            Posté par  (site web personnel) . Évalué à 3.

            f-lambda semble être une liste d'optimisation typique que l'on voit pour gcc par exemple (inlining, spécialisation, dead code elimination…).

            L'idée est de faire ça avec des conteneurs et pas seulement des constantes simples.

            "La première sécurité est la liberté"

            • [^] # Re: optimisation et propagation de constante

              Posté par  . Évalué à 3.

              Le problème n'est pas tellement sur le type de donnée: Flambda n'a aucun mal à transformer

              let c = Complex.( add { re = 2.; im = 0.} { re = 0.; im = 2. } )

              en une constante globale [| 2.; 2. |], pareillement dans le cas suivant

              let f x = 1 + x
              let rec map = function
                | [] -> []
                | [a] -> [f a]
                | [a;b] -> [f a; f b]
                | a :: q  -> f a :: map q
              let y = map [3; 4]

              la liste y est transformée en deux constantes 4::* et 5::*.

              Le problème réside plutôt au niveau du flot d'instruction: si on tient à ce que la compilation se termine toujours, il devient nécessaire de pouvoir prouver qu'un code se termine avant de pouvoir l'évaluer durant la phase de compilation. Ce n'est pas possible en général sur un langage Turing-complet.

              La voie alternative suivie par MetaOCaml est d'être très explicite sur les phases de la compilation.

              De son côté le C++ a fait le choix de laisser le choix aux compilateurs la taille de la pile de récursion lors des calcul templates.

              • [^] # Re: optimisation et propagation de constante

                Posté par  (site web personnel) . Évalué à 4.

                Il est vrai que cela dépend beaucoup des structures de données en "ligne" possible. Ocaml utilise beaucoup la liste chainé simple, elle pourrait avoir un traitement particuliers. Je pensais que la complexité venait de l'inexistence de syntaxe "statique" pour les conteneurs. Il n'y a pas d'équivalent à JSON ou aux structures de Perl en OCaml.

                La voie alternative suivie par MetaOCaml est d'être très explicite sur les phases de la compilation.

                De son côté le C++ a fait le choix de laisser le choix aux compilateurs la taille de la pile de récursion lors des calcul templates.

                D'un coté on a une impossibilité théorique forte, de l'autre un choix pragmatique. D'un coté, on a un syntaxe incompréhensible, de l'autre une syntaxe moche sauf pour les cas simples et des lib ultra puissante. D'un coté, on a un outil quasiment pas utilisé même dans la communauté ocaml et de l'autre le langage qui doit avoir le plus de ligne de code écrite, et qui est encore dans le top5 des langages utilisées malgré son age.

                Bref, le fait de mettre une limite de temps de compilation est une fonctionnalité très rationnelle, sachant qu'une compilation infinie est forcément une erreur.

                "La première sécurité est la liberté"

                • [^] # Re: optimisation et propagation de constante

                  Posté par  . Évalué à 2.

                  La comparaison me semble très subjective. La syntaxe MetaOCaml ajoute une poignée de constructions syntaxiques au langage hôte. Au contraire, la syntaxe des templates est complètement alien comparé au langage hôte. En déduire que la première syntaxe est illisible et la seconde seulement moche me paraît difficile à moins de se baser uniquement sur la familiarité. Et la syntaxe des templates est le moindre de leurs problèmes comparé à leur sémantique, ce qui n'est pas étonnant pour un langage qui n'a pas été conçu comme un langage de programmation.

                  De même, comparé l'usage de MetaOCaml à l'usage de C++ directement me semble oublier un peu rapidement que la métaprogrammation en C++ est un usage distinct et relativement peu fréquent en dehors de bibliothèques spécialisées.

                  • [^] # Re: optimisation et propagation de constante

                    Posté par  (site web personnel) . Évalué à 4.

                    Je suis d'accord que metaocaml a fait un gros effort d'unification du langage.

                    Mais il ne me semble pas qu'une lib metaocaml soit utilisable avec un code ocaml.

                    Je ne vois toujours pas pourquoi il est absolument nécessaire pour l'utilisateur d'indiquer tous les bouts de code à exécuter à la compilation, et ne pas le déduire de l'usage des constantes.

                    Le fait même de devoir utiliser un code spécifique empêche toute réutilisation du code "normal" pour son usage à la compilation.

                    "La première sécurité est la liberté"

                    • [^] # Re: optimisation et propagation de constante

                      Posté par  . Évalué à 3. Dernière modification le 04 octobre 2018 à 16:36.

                      Utiliser une bibliothèque MetaOCaml requiert certes d'utiliser un compilateur MetaOCaml, mais pas forcément d'écrire du code MetaOCaml. La bibliothèque strymonas donne cette exemple

                      let sum = fold (fun z a -> add a z) zero
                      let f arr =
                        arr 
                        |> of_arr 
                        |> map (fun x -> mul x x)
                        |> sum

                      dans lequel le map et le fold sont fusionnés durant la première phase d'évaluation sans que l'utilisateur n'ait besoin de séparer explicitement les deux phases d'exécution (tout le travail est fait par les combinateurs de la bibliothèque).

                      Je ne vois toujours pas pourquoi il est absolument nécessaire pour l'utilisateur d'indiquer tous les bouts de code à exécuter à la compilation, et ne pas le déduire de l'usage des constantes.

                      Parce que ce n'est pas parce qu'un calcul est réalisable durant la compilation, qu'il est souhaitable qu'il soit effectué à ce moment là. Par exemple dans le cadre d'une simulation scientifique, ce n'est pas parce que tous les paramètres du modèles sont connus statiquement qu'il est souhaitable d'exécuter la simulation durant la compilation. Au contraire, cela peut être pratique de pouvoir sélectionner des bouts de codes qui doivent être évalués en premier et incorporés dans le reste du code de la simulation pour pouvoir être optimisé autant que possible.

                      • [^] # Re: optimisation et propagation de constante

                        Posté par  (site web personnel) . Évalué à 3.

                        Par exemple dans le cadre d'une simulation scientifique, ce n'est pas parce que tous les paramètres du modèles sont connus statiquement qu'il est souhaitable d'exécuter la simulation durant la compilation. Au contraire, cela peut être pratique de pouvoir sélectionner des bouts de codes qui doivent être évalués en premier et incorporés dans le reste du code de la simulation pour pouvoir être optimisé autant que possible.

                        On est d'accord que c'est un cas ultra-spécifique ? J'imagine bien le cas d'usage, le cas habituel de simulateur que j'ai vu est plutôt un modèle qui load des entrées depuis un fichier.

                        "La première sécurité est la liberté"

                        • [^] # Re: optimisation et propagation de constante

                          Posté par  . Évalué à 3. Dernière modification le 04 octobre 2018 à 17:05.

                          Je ne sais pas, j'ai vu (et écrit) pas mal de simulations avec les paramètres codés en dur dans le monde académique côté physique ou chimie. Mais même dans le cas d'un modèle qui charge ses entrées depuis un fichier on peut imaginer une évaluation en trois phases:

                          • phase 1: lecture du fichier d'entrées/ configuration/ topologie du réseau
                          • phase 2: génération du code optimisé pour la configuration
                          • phase 3: évaluation de la simulation

                          qui s'accomode bien du modèle d'évaluation multistage de MetaOCaml.

                          • [^] # Re: optimisation et propagation de constante

                            Posté par  (site web personnel) . Évalué à 3.

                            Tu peux aussi avoir l'inverse, et une syntaxe de sémantique de "call" qui l'interdit à la compilation, pour ton cas spécifique.

                            C'est bizarre d'utiliser ocaml pour de la simulation numérique, non ? Il me semble que ocaml n'utilise ni openMP ni le code SSE vectoriel.

                            "La première sécurité est la liberté"

                            • [^] # Re: optimisation et propagation de constante

                              Posté par  . Évalué à 1.

                              Cela dépend de la nature de la simulation. Sur de la simulation lourde, on est bien d'accord que du fortran, C ou C++ (voire des forks académiques de ces langages) seront plus courant. Mais tant que le temps de développement reste majoritaire, OCaml me paraît tenir la route fasse à Matlab, Mathematica ou python. Et concernant le parallélisme, sur de la simulation MonteCarlo, il peut être suffisant d'avoir n jobs isolés et de terminer avec un simple reduce pour fusionner les histogrammes.

                              • [^] # Re: optimisation et propagation de constante

                                Posté par  . Évalué à -5.

                                Matlab, Mathematica et python sont utilisable par des gens qui n'ont pas fait une thèse de doctorat en informatique théorique.

                                OCaml est me semble il un échec de l'INRIA. Faudrait peut être passer au microscope un codeur lambda …

                                De part ma culture assez large, je trouve qu'il y a des choses inintéressantes dans OCaml, mais les floats non IEEE, la syntaxe compliquée, le gc et un code moins optimisé que gcc font que OCaml est cantonné à un domaine très spécifique.

                                • [^] # Re: optimisation et propagation de constante

                                  Posté par  . Évalué à 3.

                                  Matlab, Mathematica et python sont utilisable par des gens qui n'ont pas fait une thèse de doctorat en informatique théorique.

                                  Certes et quelle différence avec OCaml? Pour information, je n'ai aucun diplôme en informatique.

                                  Les flottant OCaml sont IEEE, la précision par défaut est juste 64 bits plutôt que 32.

                      • [^] # Re: optimisation et propagation de constante

                        Posté par  (site web personnel) . Évalué à 3.

                        Le cas d'usage typique que je vois, c'est une lib de regexp qui compile la regexp à la compilation et non explicitement en pseudo-code comme en C.

                        C'est de définir un DSL dont l'écriture d'un simple émulateur permet de le transformer en compilateur.

                        C'est la lecture de fichier externe, dans un autre langage (JSON, jpg) pour l'inclure dans le projet directement, pour éviter d'avoir à relire un fichier à coté du binaire à l’exécution.

                        "La première sécurité est la liberté"

                        • [^] # Re: optimisation et propagation de constante

                          Posté par  (site web personnel) . Évalué à 2. Dernière modification le 05 octobre 2018 à 07:01.

                          C'est de définir un DSL dont l'écriture d'un simple émulateur permet de le transformer en compilateur.

                          Un truc comme en Lisp donc:

                          ;; parse tree syntax
                          * (cl-ppcre:parse-string "((?<small>[a-z]*)(?<big>[A-Z]*))")
                          (:REGISTER
                           (:SEQUENCE
                            (:NAMED-REGISTER "small"
                             (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS (:RANGE #\a #\z))))
                            (:NAMED-REGISTER "big"
                             (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS (:RANGE #\A #\Z))))))

                          Voir http://edicl.github.io/cl-ppcre

                          La forme “DSL” de l'expression rationnelle peut être utilisée partout à la place d'une regexp “concrète” en syntax Perl.

                          Comme OCaml a de l'évaluation paresseuse (explicite), la manière idoine d'empêcher le programme de compiler toutes ses rexps à l'initialisation est de compiler la regexp au dernier moment.

                          • [^] # Re: optimisation et propagation de constante

                            Posté par  (site web personnel) . Évalué à 3.

                            Je ne comprend pas ce que fais ce code Lisp. C'est un parseur de regexp ?

                            "La première sécurité est la liberté"

                            • [^] # Re: optimisation et propagation de constante

                              Posté par  (site web personnel) . Évalué à 2. Dernière modification le 05 octobre 2018 à 20:10.

                              C'est un petit extrait dans la boucle d'interaction:

                              (cl-ppcre:parse-string "((?<small>[a-z]*)(?<big>[A-Z]*))")

                              J'appelle la fonction cl-ppcre:parse-string sur la chaîne de caractères "((?<small>[a-z]*)(?<big>[A-Z]*))"). Son métier est de compiler une expression rationnelle écrite en notation “perl-esque”. Le nom vient de PCRE pour Perl-Compatible Regular Expressions, une bibliothèque qui a été liée ou implémentée dans un bon paquet de langages.

                              La partie

                              (:REGISTER
                               (:SEQUENCE
                                (:NAMED-REGISTER "small"
                                 (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS (:RANGE #\a #\z))))
                                (:NAMED-REGISTER "big"
                                 (:GREEDY-REPETITION 0 NIL (:CHAR-CLASS (:RANGE #\A #\Z))))))

                              est la valeur de retour, c'est donc l'arbre syntaxique qui correspond à l'expression rationnelle. La partie (:CHAR-CLASS (:RANGE #\a #\z)) correspond visiblement à [a-z] et l'opérateur étoile est noté (:GREEDY-REPETITION 0 NIL …) et ainsi de suite.

                              La notation :REGISTER signifie juste un symbole ad-hoc – c'est en gros une chaîne de caractère immutable et c'est à peu près la même idée que les const REGISTER = 'REGISTER' qu'on peut lire en Javascript, mais en moins lourdaud. L'intérêt est que deux occurrences de :REGISTER sont toujours physiquement égales, ce qui permet de comparer rapidement les valeurs de ce type.

                              Dans le problème qui nous occupe, le module Lisp cl-ppcre permet de “lexer” à l'avance son expression rationnelle comme je viens de le montrer, pour ne plus avoir besoin de le faire à l'exécution.

                              Si on voulait vraiment compiler l'expression rationnelle à la compilation, on pourrait le faire en utilisant eval-when mais c'est un opérateur d'usage légèrement avancé.

                • [^] # Re: optimisation et propagation de constante

                  Posté par  . Évalué à 0. Dernière modification le 05 octobre 2018 à 03:26.

                  Vu l'auteur (Oleg), MetaOCaml est sûrement utilisé en production par la marine américaine.

                  • [^] # Re: optimisation et propagation de constante

                    Posté par  . Évalué à 4.

                    Oleg ne travaille plus là-bas (il est professeur d'université au Japon depuis quelques années) et il me semble qu'il y a beaucoup de groupes de recherche&développement qui travaillent sur des prototypes sans pour autant mettre du code en production dans leur structure mère. Ce que tu dis n'est pas impossible (Oleg a travaillé sur des spécialisations de bibliothèques d'algèbre linéaire et calcul numérique, c'est compatible) mais me semble drôlement spéculatif.

        • [^] # Re: optimisation et propagation de constante

          Posté par  . Évalué à 6.

          Je trouve assez gonflé de critiquer une fonctionnalité d'un langage expérimental parce que tu n'aimes pas la syntaxe. Est-ce vraiment moins lisible que HTML ou Lisp ? Un utilisateur qui a un poil l'habitude sait lire d'abord le code dans les annotations de staging (étagement ?), et voir ensuite la structure des annotations (qui indique la séparation entre le code exécution statiquement et le code produit).

           let rec power n x =
             if n = 0 then 1
             else if n mod 2 = 0 then square (power (n/2) x)
             else x * (power (n-1) x)
          
           let rec spower n x =
             if n = 0 then .<1>.
             else if n mod 2 = 0 then .<square .~(spower (n/2) x)>.
             else .<.~x * .~(spower (n-1) x)>.;;

          Tu ouvres un fil de discussion avec une question un peu naïve qui correspond à un domaine de recherche entier (le mot-clé pour chercher les références est "évaluation partielle", "partial evaluation") qui a commencé dans les années 1970. Les gens ont pas mal réfléchi à ça, poussé l'idées dans des recoins intéressants, et un consensus qui est en train de se former est que pour avoir de la prédictibilité sur les performances obtenues il faut séparer explicitement les parties évaluées statiquement et dynamiquement (et, dans le cas général, à un stage/étage ou un autre).

          Il y a des choses intéressantes qui sont faites dans ce domaine, et je trouve ça un peu triste d'ignorer une idée simplement parce que tu n'aimes pas la syntaxe. Si le sujet t'intéresse, tu devrais peut-être te renseigner sur ce que les gens ont déjà fait dessus.

          Note: MetaOCaml et les templates/constexpr n'ont pas le même but. L'idée de la méta-programmation "staged" (étagée) est de laisser l'utilisateur définir ses propres évaluations partielles, pour les calculs qui comportent un mélange entre une partie déterminée statiquement et une partie déterminée dynamiquement (par exemple: précalcul de flot de contrôle, ou autotuning "statique" du code pour une architecture spécifique). constexpr propose une forme d'évaluation statique fixée par le langage, où tout doit pouvoir être calculé statiquement. C'est facile à utiliser mais l'utilisateur ne peut pas contrôler la méthode de spécialisation. Dans le cadre plus général de la métaprogrammation template, on peut choisir des mélanges entre calcul statique et calcul dynamique, mais les possibilités sont limitées aux formes de spécialisation prévues par le compilateur du langage, plutôt que programmées directement par l'utilisateur.

          • [^] # Re: optimisation et propagation de constante

            Posté par  (site web personnel) . Évalué à 4.

            Il y a des choses intéressantes qui sont faites dans ce domaine, et je trouve ça un peu triste d'ignorer une idée simplement parce que tu n'aimes pas la syntaxe. Si le sujet t'intéresse, tu devrais peut-être te renseigner sur ce que les gens ont déjà fait dessus.

            Pourquoi crois-tu que je ne connais pas le sujet ? Je connaissais déjà metaocaml. Ce n'est pas qu'une histoire de syntaxe, mais de "langage dans le langage", de complexité inutile.

            L'idée de la méta-programmation "staged" (étagée) est de laisser l'utilisateur définir ses propres évaluations partielles,

            Oui, j'avais bien compris. Mais c'est inutile dans le cas général. Donc, à cause du cas particuliers, le truc définit quelques choses de + compliqué.

            C'est facile à utiliser mais l'utilisateur ne peut pas contrôler la méthode de spécialisation.

            Dans quel cas, cela a un intérêt pour lui ?

            "La première sécurité est la liberté"

            • [^] # Re: optimisation et propagation de constante

              Posté par  . Évalué à 5.

              Pourquoi crois-tu que je ne connais pas le sujet ?

              J'ai pensé que tu ne connaissais pas le sujet car tu as posé la question comme si tu ne connaissais pas le sujet:

              Je me demandais à quel point la propagation de constante pouvait remplacer les templates, voir même la génération de code. En général, la propagation de constante se limite aux littéraux( 10, 'c'). Pourquoi ne pas aller au delà, et faire de la propagation de constante de conteneur ?

              Imaginez la propagation d'une string qui définit une expression régulière : cela revient à générer le code lié à cette expression.

              On peut imaginer la même chose avec une structure arborescente : un interpréteur d'AST classique devient un générateur de code sur un AST statique.

              Qu'en pensez-vous ?

              J'ai supposé que, si tu savais que c'est un domaine sur lequel des gens ont travaillé, tu l'aurais indiqué en donnant un pointeur vers ces travaux (ou au moins le mot-clé à chercher : évaluation partielle, projection de Futamura, etc.), afin d'aider les gens intéressés à se renseigner plus en profondeur.

              (À relire ton message, tu mentionnes l'idée de spécialiser un interpréteur pour obtenir un compilateur, qui est assez avancée. J'aurais pu me douter que tu avais lu des choses sur le sujet, mais je me suis simplement dit que c'étaient des méditations sous la douche ou équivalent.)

        • [^] # Re: optimisation et propagation de constante

          Posté par  . Évalué à 1.

          Apparemment, il existe une extension de syntaxe qui permet de faire la même chose, mais sans besoin de MetaOCaml:
          "Staged metaprogramming in stock OCaml"
          https://github.com/stedolan/ppx_stage

    • [^] # Re: optimisation et propagation de constante

      Posté par  . Évalué à 2. Dernière modification le 30 novembre 2018 à 12:41.

      Qu'en pensez-vous ?

      Que je ne suis pas sur d'avoir compris ta question. Ce que tu demandes ressemble fort à de la spécialisation de code à la compilation, c'est à dire de l'évaluation partielle ou inlining (qui ne se limite pas à de la propagation de constante littérale) et que c'est ce que fait f-lambda.

      Quand tu passes de (fun x y -> x + y) 2 à (fun y -> 2 + y), tu fais une étape de béta-réduction, c'est-à-dire que tu exécutes le code, mais tu peux faire cela avec n'importe quelle expression du langage (pas simplement une constante comme 2) et c'est de la spécialisation de code par inlining.

      Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

      • [^] # Re: optimisation et propagation de constante

        Posté par  (site web personnel) . Évalué à 4. Dernière modification le 14 décembre 2018 à 16:03.

        Oui cela revient à ce que tu dis. Sauf qu'en général, cela ne touche pas les conteneurs, les string par exemple.

        Pour moi la spécialisation, revient à pouvoir faire plusieurs version de la même fonction selon le type réel utilisé. Ocaml et c++ m'ont convaincu que c n'était pas forcément la meilleur approche car la taille du code peut exploser. C'est plus simple de pouvoir faire de l'inlining dans les fonctions qui utilisent ces fonctions génériques.

        à priori f-lambda peut traiter ce genre d'optimisation ?

        "La première sécurité est la liberté"

        • [^] # Re: optimisation et propagation de constante

          Posté par  . Évalué à 2. Dernière modification le 14 décembre 2018 à 17:09.

          à priori f-lambda peut traiter ce genre d'optimisation ?

          A priori, j'aurais tendance à répondre oui. Flambda est une phase d'inlining beaucoup plus aggressive de la part du compilateur, avec des heuristiques pour que la taille du code spécialisé généré n'explose pas trop.

          On peut avoir, par exemple, un facteur de gain de x20 en vitesse d'exécution grâce à la spécialisation par flambda d'un code hautement fonctorisé.

          Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.

  • # Fixer les irrégularités (4e paragraphe)

    Posté par  . Évalué à 0.

    (…) ou faire évoluer le système de types en fixant des irrégularités.

    Ne vaudrait-il pas mieux les corriger voire les supprimer?

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.