problème avec s et map

Le
mpg
Re-bonjour,

Quand j'essaie de d'appliquer une substitution sur tous les éléments d'une
liste comme ça :

map (s/^(.*)..*$/$1/, @results)

ça a pas l'air de donner ce que j'attends, sans que j'arrive trop à
comprendre pourquoi. Par exemple, si j'ai envie de compactifier un peu
l'écriture de la boucle suivante :

@results = ("foo.pdf", "foo.html", "bar.pdf");
for (@results) {
s/^(.*)..*$/$1/;
print "$_";
}

(qui donne bien foo, foo, bar comme je m'y attends) en :

@results = ("foo.pdf", "foo.html", "bar.pdf");
for (map (s/^(.*)..*$/$1/, @results)) {
print "$_";
}

tout part en vrille et on dirait que $_ vaut toujours '1' dans la boucle.
Par contre, si je fais un fonction comme ça :

sub noext {
($f) = @_;
$f =~ s/^(.*)..*$/$1/;
return $f;
}
@results = ("foo.pdf", "foo.html", "bar.pdf");
for (map (&noext($_), @results)) {
print "$_";
}

Ça marche à nouveau comme je veux.

J'imagine que ça a un rapport avec le fait que s n'est pas un opérateur
comme les autres dans le sens où on écrit

$a =~ s/truc/machin;

et non pas

$a = s(truc, machin, $a);

(un peu comme on dit chomp($a) et non pas $a = chomp($a)) mais j'avoue que
c'est vraiment très embrouillé dans ma tête. Si quelqu'un pouvait
m'éclairer un peu, ça serait très gentil.

Manuel.
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Nicolas George
Le #16555351
mpg wrote in message
@results = ("foo.pdf", "foo.html", "bar.pdf");
for (map (s/^(.*)..*$/$1/, @results)) {
print "$_n";
}



(un peu comme on dit chomp($a) et non pas $a = chomp($a)) mais j'avoue que
c'est vraiment très embrouillé dans ma tête. Si quelqu'un pouvait
m'éclairer un peu, ça serait très gentil.



Oui, c'est bien ça le problème : s agit par effet de bord, en modifiant son
paramètre (implicite, $_), et pas comme une fonction en retournant un
résultat. D'ailleurs, si tu ajoutes :

print "@resultsn" à la fin du bloc que j'ai laissé, tu vois que le tableau
original lui-même a été modifié. Le print dans la boucle, lui, affiche le
résultat de s, qui est 1 si la substitution a été faite, et undef sinon.

Le résultat après map est dans $_, donc ceci devrait marcher :

for (map { s/^(.*)..*$/$1/; $_ } @results) {

Mais il y a d'autres manières de procéder. On peut ne pas utiliser s, mais
juste m pour capturer les bouts de l'expression rationnelle :

for (map /^(.*)..*/, @results) {

parce qu'en contexte liste, m renvoie la liste des expressions capturées.

Mais pour un script lisible, je trouve que faire une fonction avec un nom
clair, qui soit réutilisée partout où c'est nécessaire, est largement
préférable.
mpg
Le #16555861
Le (on) vendredi 15 août 2008 20:03, Nicolas George a écrit (wrote) :

mpg wrote in message
@results = ("foo.pdf", "foo.html", "bar.pdf");
for (map (s/^(.*)..*$/$1/, @results)) {
print "$_n";
}



print "@resultsn" à la fin du bloc que j'ai laissé, tu vois que le
tableau original lui-même a été modifié. Le print dans la boucle, lui,
affiche le résultat de s, qui est 1 si la substitution a été faite, et
undef sinon.



Ah ok. Donc déjà je vois d'où débarque ce 1. Par contre je ne suis pas sûr
de bien comprendre ce qui se passe avec map et $_. En fait je m'atais dit
que $_ contenait successivement une copie de chaque élément du tableau, que
s allait opérer sur cette copie et qu'elle deviendrait le $_ du corps.

Ce qui se passe en vrai, c'est que d'une part $_ n'est pas une copie de
chaque élément, mais une référence à chaque élément, et d'autre part le $_
du corps, c'est le résultat de la fonction passé en premier argument à map,
en non pas la valeur de $_ après évaluation de cette dernière. C'est ça ?

Le résultat après map est dans $_, donc ceci devrait marcher :

for (map { s/^(.*)..*$/$1/; $_ } @results) {



Hum, la construction avec { } se comporte comme une fonction anonyme et le
return est implicite, c'est ça ?

Mais il y a d'autres manières de procéder. On peut ne pas utiliser s, mais
juste m pour capturer les bouts de l'expression rationnelle :

for (map /^(.*)..*/, @results) {

parce qu'en contexte liste, m renvoie la liste des expressions capturées.



Ah bah oui, c'est même bien plus naturel que mon idée de départ.

Mais pour un script lisible, je trouve que faire une fonction avec un nom
clair, qui soit réutilisée partout où c'est nécessaire, est largement
préférable.



Oui, on est d'accord. Souvent je cherche la manière la plus compacte
d'écrire le truc juste parce que ça me force à comprendre certaines
particularités de Perl (ce qui m'aide en retour à lire des scripts écrits
par d'autres) mais sauf quand il s'agit de scripts jetables, j'essaie
d'écrire de façon lisible.

Manuel.
Nicolas George
Le #16556841
mpg wrote in message
et d'autre part le $_
du corps, c'est le résultat de la fonction passé en premier argument à map,
en non pas la valeur de $_ après évaluation de cette dernière. C'est ça ?



C'est à peu près ça, mais c'est dit de manière un peu trop courte. Ce qui se
passe, c'est qu'il y a deux opérations successives, qu'on pourrait
expliciter ainsi :

(je re-cite le code de départ pour référenec)
for (map (s/^(.*)..*$/$1/, @results)) {
print "$_n";
}







@liste_temporaire = map [machin], @results;

for $_ (@liste_temporaire) {
...
}

Il y a bien deux étapes : d'abord le map, qui prend successicement chaque
élément de @results, let met dans $_, calcule [machin], et stocke le
résultat dans @liste_temporaire.

Et une fois que c'est fini, il y a une deuxième boucle, qui parcourt tous
les éléments de @liste_temporaire, en les mettant dans $_, et fait le corps
de la boucle.

Et il y a en plus l'effet secondaire de map, qui est que $_ est bien un
alias sur l'élément original de @results, ce qui fait que si [machin]
modifie $_, alors l'élément de @results lui-même est modifié. Le terme
référence n'est pas bien choisi, parce que perl a une notion de références,
et que ce n'en est pas une ici, mais je ne vais pas en parler plus.

Ce qu'il faut bien comprendre, c'est qu'il y a deux boucles successives :
celle du mal et celle du for, et que le $_ qui sert d'itérateur de boucle
n'est pas le même du tout.

Une petite remarque au passage : pour un truc comme ça, on aurait tout aussi
bien pu écrire :

for my $r (@results) {
$r =~ s/.../;
print "$rn";
}

C'est probablement un peu plus efficace, parce qu'il n'y a pas besoin de
construire une liste temporaire. Notons également que dans ce cas, on peut
utiliser une autre construction pour la substitution :

$r =~ s/.[^.]z//;

qui a l'avantage de faire moins de copies.

Hum, la construction avec { } se comporte comme une fonction anonyme et le
return est implicite, c'est ça ?



Oui, c'est ça. Mais contrairement à une vraie fonction, le paramètre est
toujours passé par $_.

Oui, on est d'accord. Souvent je cherche la manière la plus compacte
d'écrire le truc juste parce que ça me force à comprendre certaines
particularités de Perl (ce qui m'aide en retour à lire des scripts écrits
par d'autres) mais sauf quand il s'agit de scripts jetables, j'essaie
d'écrire de façon lisible.



Alors je ne dis plus rien.
Paul Gaborit
Le #16557121
À (at) Fri, 15 Aug 2008 20:47:29 +0200,
mpg
Ah ok. Donc déjà je vois d'où débarque ce 1. Par contre je ne suis pas sûr
de bien comprendre ce qui se passe avec map et $_. En fait je m'atais dit
que $_ contenait successivement une copie de chaque élément du tableau, que
s allait opérer sur cette copie et qu'elle deviendrait le $_ du corps.

Ce qui se passe en vrai, c'est que d'une part $_ n'est pas une copie de
chaque élément, mais une référence à chaque élément, et d'autre part le $_
du corps, c'est le résultat de la fonction passé en premier argument à map,
en non pas la valeur de $_ après évaluation de cette dernière. C'est ça ?



Juste pour précisier : dans un appel à 'map', $_ n'est ni une copie ni
une référence de l'élément courant, c'est un *alias*. Si on modifie
$_, on modifie réellement l'élément et contrairement à une référence,
il n'y a pas besoin de le déréférencer.

--
Paul Gaborit - Perl en français -
Publicité
Poster une réponse
Anonyme