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 :
funcmain(){varaAbserf:=MyFloat(-math.Sqrt2)v:=Vertex{3,4}a=f// a MyFloat implements Absera=&v// a *Vertex implements Abser// In the following line, v is a Vertex (not *Vertex)// and does NOT implement Abser.a=vfmt.Println(a.Abs())}
la variable a est un dictionnaire de fermeture et a.Absest 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).
letfooij=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:
leti=2letbarj=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 closefoo 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:
typeAbserinterface{Abs()float64}
En OCaml, on écrirait cela ainsi :
typeabser={abs:unit->float}
mais ce type sera habité par des fermetures, c'est à dire des paires dont la fonction close aura cette forme :
type'aabser_close={abs:'a->float}
et la clôture aura cette forme :
type'aabser_closure='aabser_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 :
typeabser=Abser:'aabser_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.
[^] # Re: Trou de mémoire
Posté par kantien . En réponse au lien Go 1.18 Beta : la généricité enfin !. Évalué à 3.
Les fonctions
Abssont des fermetures, une interface c'est un dictionnaire de fermetures. Prenons le code d'usage de l'interface :la variable
aest un dictionnaire de fermeture eta.Absest une fermeture (la variable capturée étant soit unMyfloat, 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).
ici la fonction
fooest 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:ici la variable
ifait partie de l'environnement debar(on dit queiapparaît libre dansbar, là où il est lié dansfoo), cette dernière est une application partielle defoo:let bar = foo 2. La fermeture consiste à transformerbaren une paire constituée de la fonction closefooet du paramètrei = 2: une fonction non close est transformée en une fonction close, on la clôture ;-)Reprenons maintenant l'interface
Abserde l'exemple en go:En OCaml, on écrirait cela ainsi :
mais ce type sera habité par des fermetures, c'est à dire des paires dont la fonction close aura cette forme :
et la clôture aura cette forme :
autrement dit une fonction sans variable libre sur une type
'a, ainsi qu'une valeur de ce même type'acorrespondant à celle qui est capturée et encapsulée dans la clôture. Maintenant le type de départabserest équivalent à la réunion (ou somme) sur tous les types possibles des fermetures précédentes :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 unMyfloatou un*Vertexen unAbser. Maintenant, quand on veut utiliser unAbser, 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 typeAbserne 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'unAbserest 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 monomoprheint -> int -> boolen 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.