Les fonctions.

Déclaration de fonctions.

Aprés avoir vu quelques unes des fonctions prédéfinies, voyons maintenant comment l'utilisateur va pouvoir déclarer les siennes propre.
La déclaration de fonction est de la forme :

sub Nom_de_la_fonction {
  Instruction_1;
  Instruction_2;
  . . . . .
  Instruction_n;
}
  

L'appel de la fonction se fera généralement :

&Nom_de_la_fonction;

Il peut aussi se faire au moyen de l'instruction do sous la forme :

do Nom_de_la_fonction;
Programme fonc1.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub Dire_bonjour {
  print ("Bonjour la compagnie.\n");
}
&Dire_bonjour;
    
c:\progs> perl fonc1.plent
Bonjour la compagnie.
c:\progs>
    

Ainsi que nous l'avons dit, en Perl il n'y a pas de différence entre la notion de procédure et la notion de fonction.

Programme fonc2.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub Dire_bonjour {
  print ("Bonjour la compagnie.\n");
}
$a=10 + &Dire_bonjour;
print "$a";
    
c:\progs> perl fonc2.plent
Bonjour la compagnie.
11
c:\progs>
    

Le scalaire $a sa voit affecter la valeur numérique 10 auquel on ajoute le résultat de l'évaluation de la fonction &Dire_bonjour. Le seul moyen d'évaluer une fonction est de l'éxécuter, le message apparait donc sur l'écran.
Si l'exécution avait rencontré un problème le code de retour aurait été 'faux'. Dans notre cas, devant l'absence de toute erreur, il est à 'vrai' évalué à 1 car inclus dans une expression arithmétique. D'ou la valeur de $a (11).

Si rien n'est spécifié, les variables sont globales.

Programme fonc3.pl Exécution sur l'écran
#!/usr/bin/perl; 
# Declaration de la fonction
sub lecture {
  $ligne = <STDIN>;
  $ligne =~s/^\s+|\s*\n$//g;
  @nombres = split(/\s+/,$ligne);
}
$somme = 0;
&lecture;
foreach $nombre (@nombres) {
  $somme += $nombre;
}
$long = @nombres;
$moy = $somme / $long;
print ("\nSomme des $long éléments : ");
print ("$somme.\n");
print Moyenne : $moyenne.\n");
    
c:\progs> perl fonc3.plent
1 2 3 4 5 6 7 8 9

Somme des 9 éléments : 45.
Moyenne : 5.
c:\progs>
    

Arguments et valeur de retour .

Dans un premier temps, les variables étant globales sont connues de l'ensemble des blocs du programme.
La valeur de retour sera toujours la dernière valeur qui aura été évaluée.

Programme fonc4.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction<
sub exp {
  $x ** $y;
}
$x=10;
$y=2;
$a=10 + &exp;
print "Valeur de a : $a\n";
    
c:\progs> perl fonc4.plent
Valeur de a : 110
c:\progs>
    

La fonction élève la valeur contenue dans la variable $x à la puissance indiquée par $y. Cette valeur étant la dernière évaluée, elle sera retournée dans l'expression.

Programme fonc5.pl Exécution sur l'écran
#!/usr/bin/perl;     
# Declaration de la fonction
sub liste {
  ($x,$y);
}
$x=10;
$y=2;
@a = &liste;
print "Valeur de a : @a\n";
    
c:\progs> perl fonc5.plent
Valeur de a : 10 2
c:\progs>
    

Le programme fait appel à une fonction dont la dernière opération est l'évaluation d'une liste constituée des scalaires $x et $y. C'est celle liste qui sera retournée comme valeur de retour.

Programme fonc6.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub maximum {
  if ($x > $x) {
    $x;
  } else {
    $x
  }
}
$x=10;
$y=2;
@a = &maximum;
print "Valeur de a : @a\n";
    
c:\progs> perl fonc6.plent
Valeur de a : 10
c:\progs>
    

Autre exemple reprenant le programme fonc3.pl mais en utilisant la valeur de retour.

Programme fonc7.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction.
sub lecture {
  $ligne = <STDIN>;
    $ligne =~s/^\s+|\s*\n$//g;
split(/\s+/,$ligne);
}
$somme = 0;
@nombres = &lecture;
foreach $nombre (@nombres) {
  $somme += $nombre;
}
$longueur = @nombres;
$moyenne = $somme / $longueur;
print ("\nTotal des $longueur éléments : ");
print ("$somme.\n");
print ("Moyenne : $moyenne.\n");
    
c:\progs> perl fonc7.plent
1 2 3 4 5 6 7 8 9

Total des 9 éléments : 45.
Moyenne : 5.
c:\progs>
    

Il faut toutefois être très vigilant quant à la détermination de la valeur retournée, un manque de réflexion peut conduire à des surprises :

Programme fonc8.pl Exécution sur l'écran
#!/usr/bin/perl;
sub total {
  $somme = 0;
  $ligne = <stdin>;
  $ligne =~s/^\s+|\s*\n$//g;
  @nombres = split(/\s+/,$ligne);
  $index = 0;
  while ($nombres[$index] ne ""){
    $somme += $nombres[$index++];
  }
}
$somme = 0;
$total = &total;
print ("\nTotal des éléments : $total.\n");
    
c:\progs> perl fonc8.plent
1 2 3 4 5 6 7 8 9

Total des éléments :.
c:\progs>
    

Nous avons bien spécifié que la valeur de retour de la fonction était toujours la dernière valeur évaluée. Cette règle souffriait-elle de quelques exeptions?
< On serait tenté de croire que la valeur de retour de la fonction serait la somme des éléments du tableau tels qu'on les calcule dans :

while ($nombre[$index] ne ""){
  $somme += $nombres[$index++];
}
  

Ce qui ne semble pas être le cas.
Question : Quelle est la dernière valeur évaluée?
Est-ce bien comme nous le pensons :

    $somme += $nombres[$index++];
  

En fait, la réponse est non. La dernière valeur évaluée par la fonction est la condition de sortie :

   $nombre[$index] ne ""
  

Cette évaluation est fausse car c'est cette condition qui termine la boucle.
La valeur de retour est donc 'faux', ce qui, en matière de chaine se traduit par "" (chaine vide).
Pour obtenir le retour que nous souhaitons, il nous suffit avant de terminer la fonction de rajouter une évaluation de la valeur en question.

Programme fonc9.pl Exécution sur l'écran
#!/usr/bin/perl;
sub total {
  $somme = 0;
  $ligne = <stdin>;
  $ligne =~s/^\s+|\s*\n$//g;
  @nombres = split(/\s+/,$ligne);
  $index = 0;
  while ($nombres[$index] ne ""){
    $somme += $nombres[$index++];
  }
  $somme;
}
$somme = 0;
$total = &total;
print ("\nTotal des éléments : $total.\n");
    
c:\progs> perl fonc9.plent
1 2 3 4 5 6 7 8 9

Total des éléments : 45.
c:\progs>
    

Ou bien d'utiliser l'instruction

return $valeur;
Programme fonc10.pl Exécution sur l'écran
#!/usr/bin/perl;
sub total {
  $somme = 0;
  $ligne = <stdin>;
  $ligne =~s/^\s+|\s*\n$//g;
  if ($ligne eq "") {
    return ("Erreur");
  }
  @nombres = split(/\s+/,$ligne);
  $index = 0;
  while ($nombres[$index] ne ""){
    $somme += $nombres[$index++];
  }
  return $somme;
}

$total = &total;
if ($total eq "Erreur") {
  print ("Pas de données à traiter.\n");
} else{
  print ("Total des éléments: $total.\n");
}
    
c:\progs> perl fonc10.plent
1 2 3 4 5 6 7 8 9

Total des éléments : 45.
c:\progs> perl fonc10.plent
Pas de données à traiter.
c:\progs>
    

Cette instruction nous permet, ainsi que nous le voyons dans l'exemple précédent,de disposer de plusieurs sorties distinctes dans le corps de la fonction, chacune d'elle étant identifiée par sa valeur de retour.

La fonction wantarray().

Il est parfois interessant de savoir si la valeur que doit retourner procédure est une liste ou un scalaire. C'est la fonction wantarray va permettre d'effectuer de savoir quel est le type de l'objet qui se trouve à la gauche du signe d'affectation.

$resultat = wantarray();

$resultat est undef ('faux') si la procédure est sensée retourner un scalaire.
$resultat est 'vrai' si la procédure est sensée retourner une liste.

Programmefonc11.pl Exécution sur l'écran
#!/usr/bin/perl
print ("Affectation à une liste.\n");
@liste = &ma_procedure;
print ("Affectation à un scalaire.\n");
$scalaire = &ma_procedure;

sub ma_procedure {
  if (wantarray()) {
    print ("Retour de liste.\n\n");
  } else{
    print ("Retour de scalaire.\n\n");
  }
}
    
c:\progs> perl fonc11.plent
Affectation à une liste.
Retour de liste.

Affectation à un scalaire.
Retour de scalaire.
c:\progs>
    

Les variables locales.

Il est courant qu'une fonctions doive faire référence à certaines variables qui ne seront utilisées qu'à l'intérieur du bloc qui les concerne.
Dans ces conditions, il est judicieux de les déclarer comme des variables locales.
Il existe deux manières de déclarer une variable locale.
La déclaration 'my' permet de définir une variable qui n'existe que dans le corps de la fonction. Cette déclaration n'existe pas en Perl 4.
La déclaration 'local' permet de définir une variable qui n'existe pas dans le programme principal, mais qui est connue de la fonction et qui le sera aussi de toutes celles internes à cette procédure.

my $var;
my @lst = (0..9);
my ($somme, $ligne, @mots) = (0,"****","Vide");
local $s = 3,14159;
local @c;
local ($index, $retour) = (0,"Erreur");
local (@phrase) ("Ceci"," est"," une"," phrase.");
  
Programme fonc12.pl Exécution sur l'écran
#!/usr/bin/perl;
@suite = (1..10);
print ("le produit des valeurs\n");
print ("@suite\n");
print ("est egal a : ";
print &multiplier (@suite);
print "\nLa variable prod est à $prod.";

# Declaration de la fonction
sub multiplier {
  local ($prod);
  $prod = 1;
  foreach $_ (@_) {
    $prod *= $_;
  }
  $prod;
}
    
c:\progs> perl fonc12.plent
le produit des valeurs
1 2 3 4 5 6 7 8 9 10
est egal a : 3628800
La variable prod est à .
c:\progs>
    

La variable $prod est déclarée locale à la procédure elle est donc connue du corps de la fonction mais inconnue du programme.
Sa valeur est 3628800 dans le bloc correspondant à la fonction multiplier(). Elle est "" (undef) partout ailleurs.

Programme fonc13.pl Exécution sur l'écran
#!/usr/bin/perl;
# Déclaration de la fonction.
sub faire_le_total {
  my ($somme, $ligne, @mots);
  my ($index, $retour);
  $somme = 0;
  $ligne = <STDIN>;
  if ($ligne eq "") {
    return ("fini");
  }
  $ligne =~s/^\s+|\s*\n$//g;
  @mots = split(/\s+/, $ligne);
  $index = 0;
  while ($mots[$index] ne "") {
    $somme += $mots[$index++];
  }
  $retour = $somme;
}

$somme = 0;
while (1) {
  $total = &faire_le_total;
  last if ($total eq "fini");
  print ("Total de la ligne : $total.\n");
  $somme += $total;
}
print ("Total général : $somme.\n");
    
 c:\progs> perl fonc13.plent
 1 2 3ent
 Total de laligne : 6.
 4 5 6ent
 Total de laligne : 15.
 7 8 9ent
 Total de la ligne : 24.
 ctrlD
 Total général : 45.
 c:\progs>
    

La liste d'appel.

Il n'existe pas à proprement parler une liste d'appel au sens pascalien du terme. C'est par l'intermédiaire de la liste standard prédéfinie qu'une fonction paut récupérer la liste des arguments qu'un appelant souhaite lui transmettre.
Rappelons que dans la liste @_ le premier élément est $_[0], le second élément $_[1] etc...
Rappelons aussi que l'instruction

    shift (@liste);
  

Permet de récupérer l'un aprés l'autre les éléments d'une liste. Une manière simple, pour une fonction, d'itérer sur l'ensemble des éléments de la liste @_ serait :

my $a;
while ($a = shift (@_){
  # Le corps de la fonction.
  # travaille sur la variable $a
}
  

Quelques exemples.

Programme fonc14.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub Dire_bonjour_a {
  print ("Bonjour $_[0].\n");
}

&Dire_bonjour_a ("Francis");
    
c:\progs> perl fonc14.plent
Bonjour Francis.
c:\progs>
    
Programme fonc15.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub ecrire {
  print ("$_[0] $_[1] $_[2] \n");
}

&ecrire ("Adieu","monde","cruel");
    
c:\progs> perl fonc15.plent
Adieu monde cruel
c:\progs>
    
Programme fonc16.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub ecrire {
  print ("$_[0] $_[1] $_[2] \n");
}

$x = "monde";
&ecrire ("Adieu",$x,"cruel");
    
c:\progs> perl fonc16.plent
Adieu monde cruel
c:\progs>
    
Programme fonc17.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub ajouter {
  $_[0] + $_[1];
}

$x = 3; $y = 5;
print "La somme de $x et de $y est ";
print &ajouter ($x,$y);
    
c:\progs> perl fonc17.plent
La somme de 3 et de 5 est 8
c:\progs>
    
Programme fonc18.pl Exécution sur l'écran
#!/usr/bin/perl;
sub plus_grand_que {
  local ($n,@liste);
  ($n,@liste) = @_;
  local(@resultat);
  foreach $_ (@liste) {
    if ($_ > $n) {
      push (@resultat,$_);
      push (@resultat," ");
    }
  }
  @resultat;
}

@appel = (1 .. 20);
$lim = 10;
print "Liste des elements > $lim :\n";
print &plus_grand_que (10,@appel);
    
c:\progs> perl fonc18.plent
Liste des elements > 10 :
11 12 13 14 15 16 17 18 19 20
c:\progs>
    

Dans la fonction, on sépare les éléments de la liste d'appel, le premier est le critère de comparaison, le second la liste de référence.
La séparation des divers éléments de la liste d'appel en deux éléments structurés

($n,@liste) = @_;

Permet une utilisation plus aisée des paramètres.
En fait, compte tenu de la syntaxe du langage, cette écriture est équivalente à la manipulation des variables dans laquelle.
$_[0] représenterait le critère.
@_[1..$#] représenterait la liste de référence.

Cette opération aurait aussi tout aussi bien pu s'écrire :

local ($n,@liste) = @_;

Toutes les variables utilisées dans la fonction sont locales.

Généralisation de la liste d'appel.

La liste des arguments peut être composée d'un nombre inconnu de variables. Nous pouvons reprendre et légèrement modifier le programme fonc12 afin qu'il lise la liste contenent les éléments à multiplier entre eux sur le clavier.

Programme fonc19.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub multiplier {
  local $prod;
  $prod = 1;
  foreach $_ (@_) {
    $prod *= $_;
  }
  return $prod;
}

<STDIN>;
split(/ /);
print ("le produit des valeurs\n");
print ("@suite\n");
print ("est egal a : ";
print &multiplier ();
    
c:\progs> perl fonc19.plent
1 2 3 4 5 6 7 8 9 10ent
le produit des valeurs
1 2 3 4 5 6 7 8 9 10
est egal a : 3628800
c:\progs>perl fonc19.plent
1 2 3 4 5ent
le produit des valeurs
1 2 3 4 5
est egal a : 120
c:\progs>perl fonc19.plent
1 2 3ent
le produit des valeurs
1 2 3
est egal a : 6
c:\progs>
    

Dans le programme ci dessus, la liste d'appel n'existe pas, c'est @_ qui est utilisée.

Programme fonc20.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub ecrire_la_somme {
  my($n1, $n2, $n3) = @_;
  my($total);
  print ("\nNombres proposés :\n");
  print ("$n1, $n2 et $n3.\n");
  $total = $n1 + $n2 + $n3;
  print ("\nSomme : $total.\n");
}

print ("Somme de trois nombres.\n");
print ("Premier nombre ? ");
chomp ($n1 = <STDIN>);
print ("Second nombre ? ");
chop ($n2 = <STDIN>);
print ("Troisième nombre ? ");
chop ($n3 = <STDIN>);
ecrire_la_somme ($n1, $n2, $n3);
    
c:\progs> perl fonc20.plent
Somme de trois nombres.
Premier nombre ? 1285ent
Second nombre ? 32584ent
Troisième nombre ? 251468ent

Nombres proposés :
1285, 32584 et 251468.

Somme : 285337.
c:\progs>
    

Alors que dans le programme ci dessus, la liste @_ se constitue à partir des éléments de la liste d'appel de la fonction.

@_ = ($n1,$n2,$n3);

Voici pour terminer un petit exemple réunissant plusieurs particularités du langage. Le but est de lire des lignes sur le clavier, de décompter le nombre de mots qui ont été lus et le nombre total de caractères.

Programme fonc21.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub compte {
  my ($ligne,$vide) = @_;
  my ($compteur);
  if ($vide eq "") {
    @elements = split (//, $ligne);
  } else {
    @elements = split (/$vide/,$ligne);
  }
  $compteur = @elements;
}

$nbmots = $nbcar = 0;
$carvide = "";
$motvide = "\\s+";
while ($ligne = <stdin>){
  $nbcar += &compte($ligne,$carvide);
  $ligne =~s/^\s+|\s+$//g;
  $nbmots += &compte($ligne,$motvide);
}
print ("\n$nbmots mots.\n");
print ("$nbcar caractères.\n");
    
c:\progs> perl fonc21.plent
Voici un texte qui comporte plusieurs lignesent
la premiere ligneent
a laquelle succede une secondeent
et une toisiemeent
pour finir une quatriemeent
CtrlD

22 mots.
135 caractères.
c:\progs>
    

Imbrication de fonctions.

Ainsi qu'il est montré dans l'exemple qui suit, il est possible d'imbriquer les fonctions les unes dans les autres.

Programme fonc22.pl Exécution sur l'écran
#!/usr/bin/perl;
# Declaration de la fonction
sub compter {
  my ($lignes) = @_;
  my ($carvide,$motvide);
  my ($nbcars,$nbmots);
  my ($ligne,$nblignes);
  my (@retour);
  sub count {
    my ($ligne,$forme) = @_;
    my ($compte);
    if ($forme eq "") {
      @elements = split (//, $ligne);
    } else {
      @elements = split (/$forme/,$ligne);
    }
    $compte = @elements;
  }
  $carvide = "";
  $motvide = "\\s+";
  $nblignes = $nbcars = $nbmots = 0;
  while (1) {
    $ligne = <stdin>;
    last if ($ligne eq "");
    $nblignes++;
    $nbcars += &count($ligne, $carvide);
    $line =~ s/^\s+|\s+$//g;
    $nbmots += &count($ligne, $motvide);
    last if ($nblignes == $lignes);
  }
  @retour = ($nblignes, $nbmots, $nbcars);
}

print ("Nombre maximum de lignes ? \n");
chomp ($max = <STDIN>);
($nbl, $nbm, $nbc) = &compter($max);
print ("\n$nbl lignes lues\n");
print ("$nbm mots\n");
print ("$nbc caractères\n");
    
c:\progs> perl fonc22.plent
Nombre maximum de lignes ?
5ent
Ligne unent
la seconde ligneent
une toisieme ligneent
la quatriemeent
la cinquieme et derniereent

5 lignes lues
14 mots.
84 caractères.
c:\progs>
    

Précédent
Suivant