Les fermetures en Ruby
10 Décembre 2009
En tant que langage fonctionnel, Ruby se doit de gérer les fermetures, c'est-à-dire que lorsqu'on crée une fonction anonyme, celle-ci se souvient de toutes les valeurs des variables accessibles au moment de sa création. Cependant certains exemples pourraient nous faire croire que les celles-ci ne fonctionnent pas :
[ruby]
i = 0
l = lambda {i}
l.call => 0
i = 12
l.call => 12 au lieu de 0
Pour mieux comprendre, rappelons qu'une variable Ruby contient soit une référence vers l'objet désigné, soit directement sa valeur dans le cas de valeurs immédiates simples (comme les entiers). Par exemple, lors de :
[ruby] i = 12 j = i
la valeur 12 est présente deux fois en mémoire, dans
i et j, alors qu'avec :
[ruby] i = [12] j = i
on a toujours qu'un seul tableau [12], mais référencé par deux
variables.
La méthode pour réaliser des fermetures fonctionnelles, sachant que Ruby ne sauvegarde pas les valeurs, consiste alors à créer de nouvelles variables :
[ruby]
i = 0
j = i
l = lambda {j}
l.call => 0
i = 12
l.call => 0 aussi
pour copier les valeurs à conserver dans un endroit sûr, l'idéal étant que ces variables soient locales pour ne plus être modifiées à l'extérieur : dans le cas d'un exemple moins simpliste d'un tableau de lambdas renvoyant leur index :
[ruby]
# marche que si i n'est pas déjà défini
a = Array.new(10) {|i| lambda {i}}
a.collect {|l| l.call} => [0, 1, ..., 9]
Dans le cas où i n'est pas déjà défini, chaque appel de
{|i| lambda {i}} crée une nouvelle variable et on a bien le
résultat espéré. Ceci marche sans que l'interpréteur n'ait à analyser à
l'avance le contenu de la lambda : ainsi, même avec un
eval :
[ruby]
# marche que si i n'est pas déjà défini
a = Array.new(10) {|i| lambda {eval("i")}}
a.collect {|l| l.call} => [0, 1, ..., 9]
Il est cependant dommage qu'en Ruby le programme suivant :
[ruby]
i = 12
(lambda {|i| puts i}).call(42)
i => 42
ne retourne pas 12, ie que le nom des arguments muets
ait une influence hors de leur sous-fonction. Enfin, dernier piège, la
fermeture ne fonctionne pas avec for in :
[ruby]
a = []
for i in 0..9
a << lambda {i}
end
a.collect {|l| l.call} => [9, 9, ..., 9]
blog comments powered by Disqus