• [^] # Re: Trou de mémoire

    Posté par . En réponse au lien Go 1.18 Beta : la généricité enfin !. Évalué à 3.

    Je ne vois pas de fermeture dans l'exemple que tu donnes par contre ?

    Les fonctions Abs sont des fermetures, une interface c'est un dictionnaire de fermetures. Prenons le code d'usage de l'interface :

    func main() {
     var a Abser
     f := MyFloat(-math.Sqrt2)
     v := Vertex{3, 4}
     a = f // a MyFloat implements Abser
     a = &v // a *Vertex implements Abser
     // In the following line, v is a Vertex (not *Vertex)
     // and does NOT implement Abser.
     a = v
     fmt.Println(a.Abs())
    }

    la variable a est un dictionnaire de fermeture et a.Abs est une fermeture (la variable capturée étant soit un Myfloat, soit un *Vertex). Il en est de même pour n'importe quelle interface. C'est pareil en POO : un objet c'est un dictionnaire de fermetures qui partagent le même environnement (les variables d'instance). C'est un usage classique des fermetures pour faire du polymoprhisme ad-hoc, qui est celui que tu décris, avec de l'encapsulation. Cela permet d'utiliser, comme expliqué dans l'article que tu cites sur medium, le même algorithme sur différentes structures de données qui partagent un comportement commun (décrit par l'interface).

    Voyons voir ce qu'est une fermeture (je l'écris en OCaml, c'est plus simple pour moi, surtout pour la suite).

    let foo i j = 2 * i + j

    ici la fonction foo est dite close car toutes les variables qui apparaissent dans son code sont des paramètres formels de celle-ci. En revanche ce n'est plus le cas celle-ci:

    let i = 2
    let bar j = 2 * i + j

    ici la variable i fait partie de l'environnement de bar (on dit que i apparaît libre dans bar, là où il est lié dans foo), cette dernière est une application partielle de foo: let bar = foo 2. La fermeture consiste à transformer bar en une paire constituée de la fonction close foo et du paramètre i = 2 : une fonction non close est transformée en une fonction close, on la clôture ;-)

    Reprenons maintenant l'interface Abser de l'exemple en go:

    type Abser interface {
     Abs() float64
    }

    En OCaml, on écrirait cela ainsi :

    type abser = { abs : unit -> float }

    mais ce type sera habité par des fermetures, c'est à dire des paires dont la fonction close aura cette forme :

    type 'a abser_close = { abs : 'a -> float }

    et la clôture aura cette forme :

    type 'a abser_closure = 'a abser_close * 'a

    autrement dit une fonction sans variable libre sur une type 'a, ainsi qu'une valeur de ce même type 'a correspondant à celle qui est capturée et encapsulée dans la clôture. Maintenant le type de départ abser est équivalent à la réunion (ou somme) sur tous les types possibles des fermetures précédentes :

    type abser = Abser : 'a abser_closure -> abser

    Géométriquement, on peut représenter cela par un cône:

    un cône à base circulaire

    La base circulaire représente tous les types possibles du langage et le sommet est justement le type abser. Ce cône peut être vu comme un graphe orienté étiqueté, où chaque arrête va de la base vers le sommet avec comme étiquette la fonction qui calcule la valeur absolue pour le type en question. Il illustre comment on transforme un Myfloat ou un *Vertex en un Abser. Maintenant, quand on veut utiliser un Abser, il faut retourner l'orientation du graphe : on ne va plus de la base vers le sommet, mais du sommet vers la base. Alors une valeur de type Abser ne peut être utilisée quand observant la valeur qu'elle encapsule, en redescendant le long de l'arrête correspondant et en appliquant la fonction en étiquette. Ce mécanisme d'utilisation d'un Abser est ce que les programmeurs appellent le dynamic dispatch, qui est au cœur de la POO et du polymorphisme ad-hoc.

    La généricité, en revanche, permet d'appliquer le même algorithme sur un même structure de données qui est un conteneur, comme le sont les tableaux (on parle plutôt volontiers, dans ce cas, de polymorphisme structurel). Ici, vous vous en sortez avec des fermetures (comme pour la fonction de tri), parce qu'un tableau peut être vu comme un dictionnaire clef-valeur où les clefs sont des int. Ainsi, au lieu d'avoir une fonction avec un type polymorphe, qui prend une fonction de comparaison sur le type contenu dans le tableau ('a -> 'a > bool), vous pouvez vous contentez d'un type monomoprhe int -> int -> bool en encapsulant le tableau et en accédant aux valeurs par leur index. Mais cela réduit la généricité au tableaux, qui sont built-in, et cela ne permet pas de créer ses propres type génériques.

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