• [^] # Re: chaud

    Posté par . En réponse au journal Tagless-final ou l'art de l'interprétation modulaire.. Évalué à 2. Dernière modification le 23 novembre 2016 à 17:35.

    Oui, un fichier pour chaque exemple, bien séparer. Pour pouvoir comparer les évolutions et les différences. Genre je suis bloqué sur le fold appliquée aux parenthèses, mais je bloque surtout sur le principe de précédence, plus que sur le code.

    Je vais voir ce que je peux faire pour mettre du code plus détaillé et commenté sur github. Je ne te promets rien, cela dépendra de mon temps libre.

    Pour le principe de précédence qu'est-ce qui te pose problème ? On a d'abord une fonction de mise entre parenthèse sous condition :

    let cparen b = if b then paren else (fun s -> s);;
    val cparen : bool -> string -> string = <fun>

    Elle permet de retourner une fonction de type string -> string selon la valeur du booléen : soit cette fonction met des parenthèses si b = true, sinon c'est l'identité et on ne met pas de parenthèses. Ensuite pour le cas de l'addition, on a :

    let add x y = fun p -> cparen (p > 3) @@ show_add (x 3) (y 4)

    Autrement dit, tant que la priorité du contexte est ≤ 3 on ne met pas de parenthèse : 3 représente le degré de priorité de l'opérateur additif. Ensuite comme l'addition est associative à gauche, pour le membre de gauche on reste au degré 3 de l'addition, par contre pour le membre de droite on passe au degré suivant. C'est pour distinguer 1 + 2 + 3 == ((1 + 2) + 3) de 1 + (2 + 3) == (1 + (2 + 3)). Pour la multiplication, son degré de priorité est de 4 : la multiplication est prioritaire sur l'addition.

    Cela étant, c'est plus un code « standard » pour gérer la priorité des opérateurs en programmation fonctionnelle. Si tu ne vois pas trop pourquoi il fait ce qu'on lui demande, ce n'est pas très important pour le reste du journal.

    Mais pourquoi ?! En objet, c'est simplement une injection. Un composant d'un objet qui est construit à l’extérieur de celui-ci et donné en paramètre au constructeur. En quoi instancier un module en fixant un des type paramètre en fait une fonction ?

    J'ai du mal à comprendre où tu veux en venir, et je me suis peut être mal exprimé. Les implémentations de la signature d'un langage corresponde aux différentes interprétations du dit langage. Les foncteurs eux servent à écrire du code dans ses langages (comme les foncteurs d'exemples) ou à transformer les dits programmes (comme les optimiseurs). Pour moi, et comme l'a dit Thomas Douillard, c'est exactement le niveau d'abstraction qu'il me faut pour raisonner sur ce genre de problématique.

    Depuis que j'ai découvert cette méthode, et également suite aux discussions sur le journal de présentation de MetaOCaml, il y a deux comics xkcd qui résument bien mon état du moment :

    nerd snipping

    et celui-ci :

    purity

    J'ai essayé d'éviter le plus possible le recours au vocabulaire et aux concepts abstraits des mathématiques, mais ce n'est pas toujours évident.

    Pour ce qui est de l'approche final avec les objets de OCaml cela pourrait ressemblait au code suivant :

    (* on définit la symantique d'un langage avec litéraux
     * et addition comme un type d'objet paramétré par son interprétation *)
    class type ['repr] symAdd = object
     method lit : int -> 'repr
     method add : 'repr -> 'repr
    end
    (* là on a des fonctions pour créer nos termes
     * du langage à partir de ses implémentations *)
    let lit n = fun ro -> ro#lit n
    let add x y = fun ro -> ro#add (x ro) (y ro)
    (* un terme d'exemple *)
    let t1 () = add (lit 1) (add (lit 2) (lit 3))
    (* on implémente deux interprétations *)
    class eval = object
     method lit n = (n:int)
     method add x y = x + y
    end
    class view = object
     method lit n = string_of_int n
     method add x y = "(" ^ x ^ " + " ^ y ^ ")"
    end
    (* on instancie un objet pour chacune *)
    let eval = new eval
    let view = new view
    (* notre exemple est une fonction qui prend une
     * instance de classe, à la manière d'un foncteur *)
    t1 () eval;;
    - : int = 6
    t1 () view;;
    - : string = "(1 + (2 + 3))"

    Et pour étendre les langages, par exemple en rajoutant la multiplication, on fait de l'héritage entre objets :

    (* on ajoute un opérateur aux langages *)
    class type ['repr] symMul = object
     method mul : 'repr -> 'repr -> 'repr
    end
    (* la nouvelle fonction pour écrire nos termes *)
    let mul x y = fun ro -> ro#mul (x ro) (y ro)
    (* on étend par héritage nos précédents interprètes *)
    class evalM = object
     inherit eval
     method mul x y = x * y
    end
    class viewM = object
     inherit view
     method mul x y = "(" ^ x ^ " * " ^ y ^ ")"
    end
    (* on les instancie *)
    let evalM = new evalM
    let viewM = new viewM
    (* un exemple de test *)
    let t2 () = mul (add (lit 1) (lit 2)) (add (lit 3) (lit 4))
    t2 () evalM;;
    - : int = 21
    t2 () viewM;;
    - : string = "((1 + 2) * (3 + 4))"

    Cette façon de faire est plus proche de l'encodage en Haskell via les typeclasses.

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