• [^] # Re: C'est bien dommage

    Posté par . En réponse au journal C++17 est sur les rails. Évalué à 3. Dernière modification le 18 mars 2016 à 11:34.

    Un des intérêts des macros est de permettre d'étendre la syntaxe du langage, je ne vois pas en quoi c'est un problème de conception. S'il n'y a pas de mécanisme d'extension tu peux moins écrire les choses comme tu veux. Et dans le code d'Eliom (c'est peut-être là que je n'ai pas tout compris) j'ai l'impression que des outils de compilation intégrés au langage sont utilisés. Donc pas de macros mais on utilise des outils propres aux compilateurs pour étendre la syntaxe...

    Tu as bien compris, et l'intérêt de passer par des outils propres aux compilateurs pour étendre la syntaxe plutôt que d'utiliser des macros c'est :

    • uniformiser et simplifier l'écriture d'extension
    • conserver les gardes-fou du typage statique

    Jusqu'à il y a deux ans, pour étendre la syntaxe OCaml on utilisait un pré-processeur camlp4 (ce qui, au fond, est similaire à de la macro) mais c'était chiant à écrire et on se retrouvait avec des tonnes de syntaxes différentes entre les projets : la coopération entre projet était rendue plus compliqué. Avec la nouvelle façon de faire, via ppx, cela se passe au niveau de la manipulation des AST et les extensions sont uniformisées : la coopération est simplifiée et s'est accrue. Par exemple dans le cas de Eliom :

    let f x = x + 1 (* définition usuelle d'une fonction en ocaml, ici le successeur *)
    let%client f x = x + 1 (* extension de syntaxe pour dire que cette fonction est définie côté client *)
    let%server f x = x + 1 (* la même mais définie côté serveur *)
    let%shared f x = x + 1 (* elle est accessible aussi bien côté serveur que côté client *)

    Étendre la syntaxe d'un langage est une chose fort utile, mais il ne faut pas que cela défigure complètement la syntaxe initiale (ce qui est tentant avec les macros) et quitte à générer du code par modification de la syntaxe autant le faire au niveau de l'AST : ce que montre la signature du foncteur que tu cites (une signature en OCaml c'est l'équivalent des headers en C ou C++, et un foncteur est proche des templates du C++ : une fonction dont les paramètres sont des modules, i.e. des structures ayant une signature donnée).

    Les foncteurs (comme les templates en C++) est aussi une manière beaucoup plus simple et sécurisée (meilleur contrôle du typage) de générer du code. Un exemple simple, en OCaml, et la cas de la structure d'ensemble : pour construire des ensembles sur un type de donné quelconque, il suffit de le munir d'une relation d'ordre et d'utiliser le foncteur Set.Make

    (* on définit une structure d'ordre sur les entiers *)
    module IntOrd = struct
    type t = int
    let compare = Pervasives.compare (* c'est une fonction d'ordre générique sur n'importe quel type *)
    end
    (* on la passe en arguments au foncteur Set.Make *)
    module IntSet = Set.Make(IntOrd)
    (* et cela génère automatiquement toutes ces fonctions *)
    module IntSet :
    sig 
     type elt = int 
     type t = Set.Make(IntOrd).t
     val empty : t
     val is_empty : t -> bool
     val mem : elt -> t -> bool
     val add : elt -> t -> t
     val singleton : elt -> t
     val remove : elt -> t -> t
     val union : t -> t -> t
     val inter : t -> t -> t
     val diff : t -> t -> t
     val compare : t -> t -> elt
     val equal : t -> t -> bool
     val subset : t -> t -> bool
     val iter : (elt -> unit) -> t -> unit
     val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
     val for_all : (elt -> bool) -> t -> bool
     val exists : (elt -> bool) -> t -> bool
     val filter : (elt -> bool) -> t -> t
     val partition : (elt -> bool) -> t -> t * t
     val cardinal : t -> elt
     val elements : t -> elt list
     val min_elt : t -> elt
     val max_elt : t -> elt
     val choose : t -> elt
     val split : elt -> t -> t * bool * t
     val find : elt -> t -> elt
     val of_list : elt list -> t
    end
    (* que je peux utiliser dans la foulée *)
    let e = IntSet.empty (* e est l'ensemble vide *)
    let e = IntSet.add 1 e (* je lui ajoute l'élément 1 *)
    (* je teste si 1 est élément de l'ensemble *)
    InSet.mem 1 e
    - : bool = true

    Pour ce qui est du parenthésage, c'est une question de goût, mais la syntaxe de Lisp qui colle à la syntaxe du lambda-calcul n'est pas toujours des plus simples à lire. Par exemple la fonction qui consiste à échanger l'ordre d'application des arguments d'une fonction s'écrit en \lambda-calcul : \lambda z.(\lambda x.(\lambda y.(z y)x)), là où je trouve la syntaxe let swap z x y = z y x plus « parlante » et moins « lourde ».

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