13. Écrire un module

Après avoir manipulé des modules existants et avoir parlé de CPAN, nous allons maintenant apprendre à écrire nos propres modules.

13.1. Premier exemple

Pour écrire un module, nous devons créer un fichier indépendant du ou des scripts qui l'utilisent. L'extension de ce fichier est impérativement .pm : par exemple Utils.pm Ce fichier doit être placé dans un des répertoires listés dans la variable @INC ; pour commencer vous pourriez le placer dans votre répertoire de travail à côté du script qui l'utilisera, car le répertoire . est présent dans ce tableau @INC.

Ce fichier doit contenir une première ligne indiquant le nom du module ; pour cela, vous devez écrire :

package Utils;

Il est important de voir que le nom du package doit être le même que celui du fichier (à l'extension près). Le fichier peut ensuite contenir des définitions de fonctions. Voici un exemple simple d'un tout petit module complet :

# --- fichier Utils.pm ---
package Utils;
use strict;
sub bonjour
{
   my ($prenom) = @_;
   print "Bonjour $prenom\n";
}
1;

Il est important de ne pas oublier la dernière ligne, celle qui contient 1; ; nous reviendrons plus tard sur son rôle.

Pour pouvoir utiliser ce module dans un script, il est nécessaire d'invoquer l'instruction use suivie du nom du module. Voici un exemple de l'utilisation du module précédent :

#!/usr/bin/perl -w
# --- fichier script.pl ---
use strict;
use Utils;   # chargement du module
Utils::bonjour( "Paul" );

La dernière ligne correspond à l'appel de la fonction bonjour du module Utils. La syntaxe est la suivante : le nom du module est suivi de deux signes deux-points puis du nom de la fonction.

13.2. Et les variables ?

Il est possible de déclarer des variables propres au module. Ces variables seront :

  • soit accessibles exclusivement aux fonctions présentes dans le module (on pourrait parler de variables privées, correspondant aux variables static déclarées en dehors des fonctions en C),

  • soit aussi accessibles à l'extérieur du module (on pourrait parler de variables publiques ou variables globales).

Une variable accessible exclusivement aux fonctions du module se déclare avec my (que l'on connaît déjà). Une variable aussi accessible depuis l'extérieur du module se déclare avec our (my pour dire "à moi", our pour dire "à nous"). Avant la version 5.6 de Perl, on utilisait un autre mécanisme dont je ne parlerai pas ici, assurez-vous d'avoir une version récente de Perl (cf perl -v). Ces variables doivent être déclarées en dehors de toute fonction ; les variables déclarées dans les fonctions sont comme toujours locales au bloc dans lequel elles sont déclarées.

# --- fichier Utils.pm ---
package Utils;
use strict;
# variable accessible
our $x = 'toto';
# variable inaccessible
my $y = 'toto';
# fonction
sub bonjour
{
   # Variable locale
   my ($prenom) = @_;
   print "Bonjour $prenom\n";
   print "$x $y\n";
}
1;

Que la variable soit déclarée avec my ou avec our, il est tout à fait possible d'y accéder depuis une fonction du module (ici bonjour). À l'inverse, depuis l'extérieur du module, c'est-à-dire depuis le script, seule la variable $x est accessible.

#!/usr/bin/perl -w
# --- fichier script.pl ---
use strict;  
use Utils;
Utils::bonjour( "Paul" );
# Ok :
print "$Utils::x\n";
# Erreur :
print "$Utils::y\n";

De même que pour les fonctions, les noms de variable sont préfixés par le nom du module puis deux signes deux-points. Ici, le nom complet de la variable est donc Utils::x qu'il faut faire précéder d'un signe dollar, ce qui donne : $Utils::x au final. Il n'y a pas d'erreur de ma part : on n'écrit pas Utils::$x ! :-)

13.3. De la dernière ligne d'un module

Jusqu'ici, nous avons toujours placé une ligne 1; à la fin du fichier de notre module. Il faut savoir que cette valeur est la valeur du chargement du module (valeur de l'instruction use Utils;) et qu'elle indique si ce chargement s'est bien passé ou non : une valeur fausse indique un problème, une valeur vraie (comme ici 1) indique au contraire que le chargement s'est bien déroulé. Une valeur fausse mettra fin au script qui fait appel à l'instruction use.

Il est donc tout à fait possible de mettre une autre valeur qu'une valeur constante ; on peut, par exemple, envisager mettre un test en dernière instruction pour vérifier si les conditions sont réunies pour l'usage du module. On pourrait imaginer conditionner le chargement du module à l'ouverture d'un fichier, d'une connexion réseau (ou je-ne-sais-quoi encore...). Je ne donnerai pas d'exemple ici car le cas est rare de la nécessité d'un tel usage, mais il faut savoir que cela est possible.

13.4. Répertoires

Voyons maintenant comment créer des modules aux noms composés comme Truc::Utils (nous avons par exemple vu le module Net::FTP). Ces noms composés permettent de regrouper les modules par type d'usages ; par exemple Net correspond à tout ce qui concerne le réseau.

Revenons à notre exemple Truc::Utils. Ce nom Truc correspond à un répertoire qui doit être présent dans un des répertoires de la variable @INC (par exemple .) et le fichier Utils.pm doit être présent dans ce répertoire Truc.

Voici un exemple de tel module :

# --- fichier Truc/Utils.pm ---
package Truc::Utils;
use strict;
our $x = 'toto';
sub bonjour
{  
   my ($prenom) = @_;
   print "Bonjour $prenom\n";
}
1;

Et voici un script l'utilisant :

#!/usr/bin/perl -w 
# --- fichier script.pl ---
use strict;  
use Truc::Utils;
Truc::Utils::bonjour( "Paul" );
print "$Truc::Utils::x\n";

Rien de sorcier.

13.5. Blocs BEGIN et END

Les amoureux de awk retrouveront ici deux de leurs enfants préférés :-)). Dans un module, il est possible de prévoir deux blocs d'instructions qui seront exécutés soit dès le chargement du module (bloc BEGIN) soit lors de la fin de l'usage du module (bloc END).

package Utils;
use strict;
sub f
{ ...  }
BEGIN
{
   print "Chargement du module\n";
}
END
{
   print "Fin d'usage du module\n";
}
1;

Notez bien qu'il ne s'agit pas de fonctions (pas de mot clef sub), mais bien de blocs labélisés. Le bloc BEGIN sera exécuté lors de l'instruction use Utils; avant toute autre instruction du module (y compris les use placés dans le module). Le bloc END sera exécuté lors de la fin du programme.

L'usage de ces deux blocs peut être nécessaire lorsque l'utilisation du module est conditionnée par l'obtention d'une ou plusieurs ressources comme un fichier ou une connexion réseau. Ces blocs vont nous servir à préparer le terrain au début et à libérer les ressources à la fin.

Lorsque dans un module sont présentes d'autres instructions que des définitions de variables et des définitions de fonctions, ces instructions sont exécutées au moment du chargement du module. Tout se passe comme si ces instructions figurent dans un BEGIN implicite. L'usage d'un bloc BEGIN permet juste au programmeur d'écrire un code un peu plus lisible et propre, dans la mesure où toutes ces instructions sont regroupées sous un nom (BEGIN) qui rappelle explicitement qu'elles sont exécutées au début.

Notez bien que la programmation objet (lire la suite) a quelque peu rendu ces deux blocs obsolètes voire inutiles.

13.6. Introduction à l'export de symboles

Sous ce titre barbare se cache une idée simple : il peut être pénible de toujours écrire le nom complet des fonctions de modules. Je veux dire par là que d'écrire Utils::bonjour à chaque fois que vous voulez appeler cette fonction est sans doute lourd et est quelque peu pénible à la longue. Il existe un moyen pour n'avoir qu'a écrire bonjour sans avoir à rappeler le nom du module qui contient la fonction. En faisant cela nous allons ajouter la fonction dans l'espace de nommage du script.

Placer une fonction ou une variable d'un module dans l'espace de nommage d'un script ou d'un autre module s'appelle faire un export, on parle d'exporter le symbole (fonction ou variable). Ce symbole est donc importé par le script.

Pour avoir la capacité d'exporter des symboles, notre module futur-exportateur doit comporter les lignes suivantes :

package Utils;
use Exporter;
our @ISA = qw(Exporter);

Ces deux nouvelles lignes d'instructions doivent être placées juste après l'instruction package. La première est l'invocation du module Exporter ; avec la seconde on indique que notre module est un (ISA) Exporter (nous reverrons cette syntaxe et ses implications en programmation objet). Notre module est maintenant capable d'exporter des symboles.

Il existe quatre types de symboles (fonctions ou variables) :

  • ceux qui sont exportés pas défaut : le script utilisant le module n'a rien besoin de faire de spécial (autre que de faire le use) pour que ces symboles soient exportés,

  • ceux qui sont individuellement exportables en fonction de ce que demande le script utilisateur,

  • ceux qui sont exportables en groupe (on parle de tags) selon ce que demande le script utilisateur,

  • ceux qui ne sont pas exportables (c'est-à-dire qu'il faudra toujours faire précéder leur nom par le nom complet du module).

Chacun des trois premiers ensembles est associé à une variable déclarée avec our.

13.7. Export par défaut de symboles

Les symboles exportés par défaut doivent être listés dans la variable @EXPORT ; il s'agit donc d'un tableau. Il est courant d'initialiser ce tableau avec l'opérateur qw que nous avons déjà vu et sur lequel je ne reviendrai donc pas :

our @EXPORT = qw(&bonjour &hello $var);

Cette ligne placée dans le module Utils à la suite de la ligne

our @ISA = qw(Exporter);

va permettre d'utiliser les fonctions bonjour et hello ainsi que la variable scalaire $var sans préfixe dans le script utilisateur. Notez bien que, si les variables doivent être citées avec leur caractère de différentiation de type (le dollar, l'arobase ou le pourcentage), il en est de même avec les fonctions et le signe "et-commercial" (&). Sachez juste que ce et-commercial peut être omis.

Voici comment on peut maintenant utiliser le module en question :

use Utils;
bonjour("Paul");
hello("Peter");
print "$var\n";

Plutôt simple.

13.8. Export individuel de symboles

Un symbole peut être exporté à la demande de celui qui utilise le module. C'est-à-dire que ce symbole n'est pas exporté par défaut, mais il peut faire l'objet d'un export s'il est nommément cité lors de l'instruction use.

Un symbole doit apparaître dans la variable @EXPORT_OK pour être autorisé à être exporté :

our @EXPORT_OK = qw(&gutenTag &ciao $var2);

Ces trois symboles sont maintenant exportables dans le script utilisant ce module. Pour cela il convient d'ajouter une liste de symboles à l'instruction use :

use Utils qw(&ciao $var2);
ciao("Paula");
print "$var2\n";

Cette ligne importe donc les symboles demandés et ces derniers sont donc utilisables sans préfixe.

Il se trouve qu'une telle ligne n'importe plus les symboles par défaut (ceux de la variable @EXPORT) ; ne me demandez pas pourquoi, je trouve cela aussi stupide que vous ... Pour remédier à cela, il nous faut ajouter à la liste des imports le tag :DEFAULT :

use Utils qw(:DEFAULT &ciao $var2); 
bonjour("Paul");
hello("Peter");
print "$var\n";
ciao("Paula");
print "$var2\n";

Ce mécanisme de sélection des symboles exportés permet à l'utilisateur du module de ne pas trop polluer son espace de nommage et de choisir les seules fonctions dont il aura besoin.

13.9. Export par tags de symboles

Il est possible de regrouper les symboles dans des tags. Un tag est une liste de symboles. L'import d'un tag provoque l'import de tous les symboles composant ce tag. La variable qui entre ici en jeu est %EXPORT_TAGS ; il s'agit donc d'une table de hachage. À chaque tag est associée une référence vers un tableau contenant la liste des symboles du tag :

our %EXPORT_TAGS=(T1=>[qw(&ciao &gutenTag)],
                  T2=>[qw(&ciao $var2)]);

Le tag T1 est associé aux fonctions ciao et gutenTag. Le tag T2 est associé à la fonction ciao et à la variable $var2. Le nom des tags est par convention en majuscules.

Remarque importante : les symboles présents dans les listes associées aux tags doivent absolument être présents dans @EXPORT et/ou @EXPORT_OK. Dans le cas contraire, leur export sera impossible.

Voici un usage de ce module :

use Utils qw(:T2);
ciao("Paula");
print "$var2\n";

Le nom du tag est placé dans la liste des modules précédé par le signe deux-points. Il est possible de combiner les différents types d'accès :

use Utils qw(:DEFAULT &ciao :T1);
bonjour("Paul");
hello("Peter");
print "$var\n";
ciao("Paula");
print "$var2\n";
gutenTag("Hans");

On voit alors que DEFAULT est un tag.

13.10. Exemple complet d'exports

Voici un exemple complet d'usage des exports dans les modules ; j'ai essayé de regrouper toutes les configurations.

Voici le module Utils dans le fichier Utils.pm :

package Utils;
use strict;
use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(&f1 &f2);
our @EXPORT_OK = qw(&f3 &f4 &f5 &f6);
our %EXPORT_TAGS = (T1 => [qw(&f5 &f6)],
                    T2 => [qw(&f4 &f6)]);
sub f1 { print "f1\n"; }
sub f2 { print "f2\n"; }
sub f3 { print "f3\n"; }
sub f4 { print "f4\n"; }
sub f5 { print "f5\n"; }
sub f6 { print "f6\n"; }
1;

Et voici un script l'utilisant (après chaque appel de fonction est signalée la raison qui fait qu'il est possible de l'appeler sans préfixe) :

#!/usr/bin/perl -w
use strict;
use Utils qw(:DEFAULT :T2 &f3);
f1();         # tag DEFAULT
f2();         # tag DEFAULT
f3();         # individuellement
f4();         # tag T2
Utils::f5();  # pas importée
f6();         # tag T2

Notez bien le cas de la fonction f5 qui n'est pas importée, mais qui n'en reste pas moins utilisable.

Pour plus d'informations et d'exemples je vous invite à vous référer à perldoc Exporter.

13.11. Fonctions inaccessibles

Vous allez me poser la question suivante : comment faire pour rendre une fonction d'un module inaccessible depuis le script ? Et je vous répondrai : cela n'est a priori pas possible (vous allez voir que si finalement).

Vous l'avez compris, Perl n'est pas un langage extrêmement coercitif, notamment par rapport à des langages comme Java ou C++. Il n'y a donc rien de prévu dans le langage pour rendre certaines fonctions uniquement accessibles depuis l'intérieur du module. Est alors apparue la convention suivante : toute fonction ou variable dont le nom commence par un souligné (ou under-score '_') est privée et ne doit pas être utilisée à l'extérieur du module.

Cette confiance en l'utilisateur du module est souvent suffisante et les modules CPAN sont bâtis sur ce modèle.

Néanmoins, si vous êtes outré par ce que je viens d'écrire car vous êtes un fanatique de Java ou autres, il existe un moyen d'écrire des fonctions vraiment internes aux modules. Il faut pour cela déclarer une variable avec my (donc invisible depuis l'extérieur du module) et d'en faire une référence anonyme vers fonction :

package Utils;
use strict;
my $affiche = sub
{
   my ($n,$m) = @_;
   print "$n, $m\n";
}; 

La variable $affiche est donc une variable privée qui pointe vers une fonction anonyme. Son usage est donc réservé aux fonctions déclarées dans le module :

sub truc
{
   $affiche->(4,5);
}

Remarquez que, comme le code Perl est toujours accessible en lecture, il est toujours possible à l'utilisateur du module de prendre le code en copier-coller et d'en faire une fonction personnelle ... Perl n'est pas fait pour les paranoïaques.

13.12. Documentation des modules

Documenter son travail est important pour une ré-utilisation du code par d'autres ou même par soi-même plus tard ... En Perl la documentation des modules se fait dans le code même du module. Une syntaxe particulière, nommée POD, permet cela. Les instructions POD commencent toujours par le signe égal (=).

La documentation d'un module commence typiquement ainsi :

=head1 NAME

Utils.pm - Useful functions

=head1 SYNOPSIS

 use Utils;
 bonjour("Paul");

=head1 DESCRIPTION

 Blabla blabla

=head2 Exports

=over

=item :T1 Blabla

=item :T2 Blabla

=back

=cut

Les tags =head1 définissent des en-têtes de premier niveau (des gros titres) et les tags =head2 définissent des en-têtes de deuxième niveau (des sous-titres). Il est de coutume de mettre les premiers exclusivement en majuscules. Les tags =over, =item et =back permettent de mettre en place une liste. Le reste du texte est libre. Le tag =cut indique la fin du POD.

Les blocs de POD et les portions de code peuvent alterner : cela est même recommandé de documenter une fonction et d'en faire suivre le code. Pour cela vous devez savoir que l'apparition en début de ligne d'un tag POD indique la fin temporaire du code Perl et le début d'un bloc de documentation. La fin de ce POD est signalée à l'aide du tag =cut et le code Perl peut alors reprendre.

package Utils;

=head1 FUNCTION hello

 This function prints hello.

=cut

sub hello
{
   my ($firstName) = @_;
   print "Hello $firstName\n";
}

=head1 FUNCTION bonjour

 This function prints hello in french.

=cut
sub bonjour
{
   my ($prenom) = @_;
   print "Bonjour $prenom\n";
}

Comment visualiser une telle documentation, allez-vous me demander ? Rien de plus simple : perldoc est notre allié ! Tapez donc perldoc Utils (ou tout autre nom que vous aurez choisi de donner à votre module) et sa documentation apparaît au format man comme tout bon module CPAN. Quoi de plus simple ?

NAME
       Utils.pm - Useful functions

SYNOPSIS
       use Utils;
       bonjour("Paul");

DESCRIPTION
       Blabla blabla

       Exports

       :T1 Blabla
       :T2 Blabla

FUNCTION hello
       This function prints hello.

FUNCTION bonjour
       This function prints hello in french.

Pour plus d'informations et de détails sur ce format, je vous invite à consulter perldoc perlpod où de nombreux exemples sont donnés. Vous pouvez aussi jeter un oeil au code d'un module ou deux dont le perldoc vous intrigue ...

13.13. Un dernier mot sur les modules

J'espère que la lecture de cette partie vous a donné envie de structurer votre code en regroupant vos fonctions dans de telles bibliothèques. Vous vous rendrez compte de cette nécessité lorsque vous aurez un fichier de code trop gros à gérer ... Mais même sans cela, n'hésitez pas à faire des modules, ne serait-ce que pour une ré-utilisation de fonctionnalités dans d'autres scripts ou pour partager votre code avec des amis, des collègues voire avec la communauté.