Perl moderne : 2014

Prenez le contrôle de votre programmation avec le Perl moderne.


précédentsommairesuivant

XII. Que faut-il éviter ?

Perl n'est pas parfait. Certaines fonctionnalités sont difficiles à utiliser correctement et d'autres semblaient très bien, mais ne fonctionnent pas si bien que cela. Quelques caractéristiques se combinent avec d'autres de façons étranges et donnent des cas limites étranges. La connaissance des aspérités de Perl vous aidera à les éviter, si possible, et à éviter les aspérités lorsque vous devez les utiliser.

XII-A. Mots nus

Perl est un langage malléable. Vous pouvez écrire des programmes dans n'importe quel style créatif, maintenable, obscur ou bizarre que vous préférez. Les bons programmeurs écrivent du code qu'ils veulent maintenir, mais Perl ne décidera pas à votre place ce que vous jugez maintenable.

Le compilateur Perl comprend les fonctions internes et les opérateurs de Perl. Il utilise des sigils pour identifier les variables et autres signes de ponctuation pour reconnaître les appels de fonctions et de méthodes. Pourtant, parfois, le compilateur doit deviner ce que vous voulez dire, en particulier lorsque vous utilisez un mot nu (bareword) — un identifiant sans un sigil ou une autre ponctuation syntaxiquement significative.

XII-A-1. Bons usages des mots nus

Bien que le pragma strict (PragmasPragmas) interdise à juste titre les mots nus ambigus, certains mots nus sont acceptables.

XII-A-1-a. Mots nus comme clés de hachage

Généralement, les clés de hachage en Perl ne sont pas ambiguës parce que l'analyseur syntaxique peut les identifier comme des chaînes ; flipper dans $jeux{flipper} est évidemment une chaîne.

Parfois, cette interprétation n'est pas ce que vous voulez, surtout quand vous avez l'intention d'évaluer une commande ou une fonction pour produire la clé de hachage. Pour rendre ces cas clairs, passez des arguments à la fonction ou utilisez des parenthèses, ou préfixez-les d'un plus unaire pour forcer l'évaluation de la commande :

 
Sélectionnez
# le 'shift' littéral est la clé
my $valeur = $elements{shift};

# la valeur produite par shift est la clé
my $valeur = $elements{shift @_}

# la fonction retourne la clé
my $valeur = $elements{myshift( @_ )}

# le plus unaire utilise la fonction shift
my $valeur = $elements{+shift};

XII-A-1-b. Mots nus comme noms de paquetage

Les noms de paquetage sont également des mots nus. Si vos conventions de nommage statuent que les noms de paquetage commencent par une majuscule et pas les fonctions, vous rencontrerez rarement des collisions de noms. Même encore, Perl doit déterminer comment analyser Package->method. Est-ce que cela veut dire « appelle une fonction nommée Package(), puis appelle method() sur sa valeur de retour » ou « appelle une méthode nommée method() de l'espace de noms Package » ? La réponse dépend du code que vous avez déjà compilé.

Forcez le compilateur à traiter Package comme un nom de paquetage en ajoutant le séparateur de paquetage (::) (même parmi les gens qui comprennent pourquoi cela fonctionne, très peu le font.) ou faites-en une chaîne littérale :

 
Sélectionnez
# probablement une méthode de classe 
Package->method;

# certainement une méthode de classe
Package::->method;

# une méthode de classe un peu moins laide
'Package'->method;

XII-A-1-c. Mots nus comme noms de bloc de code

Les blocs de code nommés spéciaux AUTOLOAD, BEGIN, CHECK, DESTROY, END, INIT, et UNITCHECK sont des mots nus qui déclarent des fonctions sans la commande sub. Vous avez vu cela auparavant (Génération de codeGénération de code) :

 
Sélectionnez
package Singe::Butler;

BEGIN { initialiser_simiens( __PACKAGE__ ) }

sub AUTOLOAD { ... }

Alors que vous pouvez déclarer AUTOLOAD() sans utiliser sub, peu de gens le font.

XII-A-1-d. Mots nus comme constantes

Les constantes déclarées avec le pragma constant sont utilisables comme mots nus :

 
Sélectionnez
# n'utilisez pas ceci pour une vraie authentification
use constant NAME     => 'Bucky';
use constant PASSWORD => '|38fish!head74|';

return unless $nom eq NAME && $pass eq PASSWORD;

Ces constantes ne sont pas interpolées dans des chaînes entre guillemets doubles.

Les constantes sont un cas particulier de fonctions prototypées (PrototypesPrototypes). Lorsque vous prédéclarez une fonction avec un prototype, le compilateur traitera spécialement toutes les utilisations ultérieures de ce mot nu et vous avertira sur les erreurs ambiguës d'analyse lexicale ou syntaxique. Tous les autres inconvénients de prototypes s'appliquent toujours.

XII-A-2. Utilisations malavisées des mots nus

Même si vous codez très prudemment, les mots nus produisent toujours du code ambigu. Vous pouvez éviter les pires abus, mais vous rencontrerez plusieurs types de mots nus dans le code hérité.

XII-A-2-a. Mots nus comme valeurs de hachage

Certains programmes anciens peuvent ne pas prendre la peine de mettre entre guillemets les valeurs des paires de hachage :

 
Sélectionnez
# mauvais style; ne l'utilisez pas
my %parents =
(
    mere => Annette,
    pere => Floyd,
);

Si aucune des fonctions Floyd() et Annette() n'existe, Perl interprétera ces mots nus comme des chaînes. Le pragma strict 'subs' produira une erreur dans cette situation.

XII-A-2-b. Mots nus comme appels de fonction

Le code écrit sans strict 'subs' peut utiliser des mots nus comme noms de fonction. L'ajout des parenthèses dans l'appel de la fonction permettra au programme de satisfaire aux exigences de strict. Utilisez perl -MO=Deparse,-p (voir perldoc B::Deparse) pour découvrir comment Perl les analyse, puis mettez entre parenthèses en conséquence.

XII-A-2-c. Mots nus comme descripteurs de fichiers

Avant l'introduction des descripteurs de fichiers lexicaux (Références de descripteur de fichierRéférences de descripteur de fichier), tous les descripteurs de fichiers et de répertoires utilisaient des mots nus. Vous pouvez presque toujours réécrire ce code en toute sécurité en utilisant des descripteurs de fichiers lexicaux. Le compilateur Perl reconnaît les exceptions spéciales de STDIN, STDOUT et STDERR.

XII-A-2-d. Mots nus comme fonctions de tri

Enfin, le premier opérande de la fonction interne sort peut être le nom d'une fonction à utiliser pour le tri. Bien que ce soit rarement ambigu pour le compilateur, cela peut dérouter les lecteurs humains. La possibilité de fournir une référence de fonction dans un scalaire est un peu meilleure :

 
Sélectionnez
# style mot nu
my @sorted = sort comparer_longueurs @unsorted;

# référence de fonction dans un scalaire
my $comparaison = \&comparer_longueurs;
my @sorted      = sort $comparaison @unsorted;

La seconde option permet d'éviter l'utilisation d'un mot simple, mais le résultat est plus long. Malheureusement, le compilateur Perl ne comprend pas la version en une seule ligne ci-dessous en raison de l'analyse spéciale de sort ; vous ne pouvez pas utiliser une expression arbitraire (par exemple, en prenant une référence à une fonction nommée) là où un bloc ou un scalaire pourraient autrement passer.

 
Sélectionnez
# ne fonctionne pas
my @sorted = sort \&comparer_longueurs @unsorted;

Dans les deux cas, la façon de sort d'appeler la fonction et de lui fournir des arguments peut être source de confusion (voir perldoc -f sort pour les détails). Si possible, envisagez d'utiliser à la place la forme de bloc de sort. Si vous devez utiliser une forme de fonction, ajoutez un commentaire à propos de ce que vous faites et pourquoi.

XII-B. Objets indirects

Perl n'a pas d'opérateur new. Un constructeur est quelque chose qui retourne un objet. Par convention, les constructeurs sont des méthodes de classe nommées new(), mais vous pouvez nommer ces méthodes autrement (ou même utiliser des fonctions). Plusieurs anciens tutoriels Perl OO promeuvent l'utilisation des appels du constructeur du style C++ et Java :

 
Sélectionnez
my $q = new CGI; # NE PAS UTILISER

... au lieu d'un appel explicite de méthode :

 
Sélectionnez
my $q = CGI->new;

Ces syntaxes produisent un comportement équivalent... sauf quand elles ne le font pas.

XII-B-1. Mots nus comme appels indirects

Dans la forme d'objet indirect (ou, plus précisément, le cas datif) du premier exemple, le verbe (la méthode) précède le substantif (l'objet) auquel il se réfère. Cela est très bien dans les langues parlées, mais introduit des ambiguïtés d'analyse syntaxique en Perl.

Comme le nom de la méthode est un mot nu (Mots nusMots nus), le compilateur utilise plusieurs heuristiques pour deviner l'interprétation correcte de ce code. Bien que ces heuristiques soient bien testées et presque toujours justes, leurs modes de défaillance sont source de confusion. Les choses s'aggravent lorsque vous passez des arguments à un constructeur :

 
Sélectionnez
# NE PAS UTILISER
my $obj = new Classe( arg => $valeur );

Dans cet exemple, le nom de la classe ressemble à un appel de fonction. Perl peut lever l'ambiguïté de grand nombre de ces cas, mais ses heuristiques dépendent des noms de paquetage que le compilateur a vus, des mots nus qu'il a déjà résolus (et comment il les a résolus) et des noms des fonctions déjà déclarées dans le paquetage actuel. Pour une liste exhaustive de ces conditions, vous devez lire le code source du compilateur Perl — pas quelque chose que le programmeur Perl moyen veut faire.

Imaginez un problème avec une fonction prototypée (PrototypesPrototypes) dont le nom se trouve par accident entrer en conflit avec le nom d'une classe ou d'une méthode appelée indirectement. Cela est rare (c'est arrivé à votre auteur en utilisant le module JSON), mais tellement désagréable à déboguer qu'il vaut mieux d'éviter les appels indirects.

XII-B-2. Limitations scalaires de la notation indirecte

Un autre danger de la syntaxe indirecte est que le compilateur attend comme objet une expression scalaire unique. L'écriture dans un descripteur de fichier stocké dans une variable agrégat (tableau ou hachage) semble évidente, mais ne l'est pas :

 
Sélectionnez
# NE FONCTIONNE PAS COMME CECI
say $config->{output} 'Message de diagnostic amusant !';

Perl tentera d'appeler say sur l'objet $config.

Toutes les commandes internes fonctionnant sur des descripteurs de fichiers — print, close et say — fonctionnent d'une façon indirecte. C'était bien quand les descripteurs de fichiers étaient des variables globales de paquetage, mais les descripteurs lexicaux de fichiers (Références de descripteur de fichierRéférences de descripteur de fichier) mettent en évidence les problèmes de syntaxe objet indirecte. Pour résoudre ce problème, levez l'ambiguïté de la sous-expression qui produit l'invoquant prévu :

 
Sélectionnez
say {$config->{output}} 'Message de diagnostic amusant !';

XII-B-3. Alternatives à la notation indirecte

La syntaxe d'invocation directe ne souffre pas de ce problème d'ambiguïté. Pour construire un objet, appelez la méthode constructeur directement sur le nom de la classe :

 
Sélectionnez
my $q   = Plack::Request->new;
my $obj = Class->new( arg => $value );

Cette syntaxe présente toujours un problème de mot nu dans le sens où, si vous avez une fonction nommée Request dans l'espace de noms de Plack, Perl interprétera le nom nu de classe comme un appel à la fonction, comme ceci :

 
Sélectionnez
sub Plack::Request;

# vous avez écrit Plack::Reuqest->new, mais Perl a compris
my $q = Plack::Request()->new;

Même si cela arrive rarement, vous pouvez lever l'ambiguïté des noms de classe en ajoutant le séparateur de paquetage (::) ou en marquant explicitement les noms de classe comme des chaînes littérales :

 
Sélectionnez
# séparateur de paquetage
my $q = Plack::Request::->new;

# une chaîne littérale sans ambiguïté
my $q = 'Plack::Request'->new;

Presque personne ne fait cela.

Pour les cas limités aux opérations sur descripteurs de fichiers, l'utilisation du datif est tellement répandue que vous pouvez utiliser l'approche de l'appel indirect si vous entourez par des accolades votre invoquant prévu. Si vous utilisez Perl 5.14 ou plus récent (ou si vous chargez IO::File ou IO::Handle), vous pouvez utiliser des méthodes sur descripteurs lexicaux de fichiers. Presque personne ne le fait pour print et say.

Le module Perl::Critic::Policy::Dynamic::NoIndirect du CPAN (un plug-in pour Perl::Critic) peut analyser votre code pour trouver les appels indirects. Le module indirect du CPAN permet d'identifier et d'interdire leur utilisation dans des programmes en cours d'exécution :

 
Sélectionnez
# avertit sur l'utilisation des appels indirects
no indirect;

# signale une exception lors de leur utilisation
no indirect ':fatal';

XII-C. Prototypes

Un prototype est une métadonnée attachée à une fonction ou variable. Un prototype de fonction change la façon dont le compilateur Perl comprend la fonction.

Les prototypes permettent aux utilisateurs de définir leurs propres fonctions qui se comportent comme des commandes internes. Examinez la commande interne push, qui prend comme paramètres un tableau et une liste. Bien que Perl aplatisse normalement le tableau et la liste en une liste unique passée à push, l'analyseur sait traiter le tableau comme un conteneur, sans aplatir ses valeurs. En fait, tout se passe comme si on avait passé à push une référence de tableau suivie d'une liste de valeurs. Le comportement du compilateur permet à push de modifier les valeurs du conteneur.

Les prototypes de fonction sont rattachés aux déclarations de fonctions :

 
Sélectionnez
sub toto        (&@);
sub titi        ($$) { ... }
my  $tata = sub (&&) { ... };

Tout prototype attaché à une déclaration anticipée doit correspondre au prototype attaché à la déclaration de fonction. Perl émettra un avertissement si ce n'est pas le cas. Étrangement, vous pouvez omettre le prototype d'une déclaration anticipée et l'inclure pour la déclaration complète, mais il n'y a aucune raison de faire cela.

La commande interne prototype prend le nom d'une fonction et retourne une chaîne représentant son prototype.

Pour voir le prototype d'une commande interne, utilisez la forme CORE:: du nom de la fonction interne comme opérande de prototype :

 
Sélectionnez
$ perl -E "say prototype 'CORE::push';"
\@@
$ perl -E "say prototype 'CORE::keys';"
\%
$ perl -E "say prototype 'CORE::open';"
*;$@

prototype retournera undef pour les commandes internes dont vous ne pouvez pas émuler les fonctions :

 
Sélectionnez
say prototype 'CORE::system' // 'undef'
# undef; impossible d’émuler la commande system

say prototype 'CORE::prototype' // 'undef'
# undef; la commande prototype n'a pas de prototype

Vous rappelez-vous le prototype de push ?

 
Sélectionnez
$ perl -E "say prototype 'CORE::push';"
\@@

Le caractère @ de ce prototype représente une liste. La barre oblique inverse impose l'utilisation d'une référence de l'argument correspondant. Ce prototype signifie que push prend une référence de tableau et une liste de valeurs. Vous pourriez écrire mypush comme ceci :

 
Sélectionnez
sub mypush (\@@)
{
    my ($array, @rest) = @_;
    push @$array, @rest;
}

Parmi les autres caractères de prototypes, on trouve $ pour forcer un argument scalaire, % pour marquer un hachage (le plus souvent utilisé comme référence), et & pour identifier un bloc de code. Voir perldoc perlsub pour plus d'informations.

XII-C-1. Le problème avec les prototypes

Les prototypes changent la façon dont Perl analyse votre code et produit une coercition sur les arguments de vos fonctions. Alors que ces prototypes peuvent ressembler superficiellement à des signatures de fonction dans d'autres langages, ils sont très différents. Ils ne documentent ni le nombre ni les types d'arguments que les fonctions attendent, ni n’effectuent le mappage entre arguments et paramètres nommés.

Les coercitions de prototype fonctionnent de façon subtile, comme l'application du contexte scalaire sur des arguments reçus :

 
Sélectionnez
sub egalite_numerique($$)
{
    my ($left, $right) = @_;
    return $left == $right;
}

my @nombres = 1 .. 10;

say 'Sont égaux, quoi que cela signifie !'
    if egalite_numerique @nombres, 10;

... mais fonctionnent uniquement sur des expressions simples :

 
Sélectionnez
sub mypush(\@@);

# erreur de compilation : prototype non concordant
# (attend un tableau, reçoit l'affectation d'un scalaire)
mypush( my $elems = [], 1 .. 20 );

Pour déboguer cela, les utilisateurs de mypush doivent connaître à la fois si un prototype existe, et les limitations du prototype de tableau.

Débogage des erreurs de prototypes

Si vous pensez que ce message d'erreur est impénétrable, attendez jusqu'à ce que vous voyez des erreurs complexes de prototypes.

XII-C-2. Bons usages des prototypes

Les prototypes ont quelques bons usages qui l'emportent sur leurs problèmes. Par exemple, vous pouvez utiliser une fonction prototypée pour redéfinir une des commandes internes de Perl. Vérifiez d'abord si vous pouvez redéfinir la commande interne en examinant son prototype dans un petit programme de test. Ensuite, utilisez le pragma subs pour dire à Perl que vous envisagez de redéfinir une commande interne, et enfin déclarez votre redéfinition avec le prototype correct :

 
Sélectionnez
use subs 'push';

sub push (\@@) { ... }

Faites attention, le pragma subs reste en vigueur pour le reste du fichier, indépendamment de toute portée lexicale.

La deuxième raison d'utiliser les prototypes est de définir des constantes lors de la compilation. Lorsque Perl rencontre une fonction déclarée avec un prototype vide (par opposition à aucun prototype) et si cette fonction est évaluée à une seule expression constante, l'optimiseur transformera tous les appels à cette fonction en des constantes au lieu d'appels de fonction :

 
Sélectionnez
sub PI () { 4 * atan2(1, 1) }

Tout le code ultérieur utilisera la valeur calculée de pi à la place du mot nu PI ou d'un appel à PI(), en respectant la portée et la visibilité.

Le pragma standard constant gère ces détails pour vous. Le module Const::Fast du CPAN crée des scalaires constants que vous pouvez interpoler dans des chaînes.

Une utilisation raisonnable des prototypes est d'étendre la syntaxe de Perl pour fonctionner sur des fonctions anonymes en tant que blocs. Le module Test::Exception du CPAN l'utilise à bon escient pour fournir une belle API avec calcul retardé. Voir aussi Test::Fatal. Sa fonction throws_ok() accepte trois arguments : un bloc de code à exécuter, une expression régulière pour rechercher le message d'exception et une description facultative du test :

 
Sélectionnez
use Test::More;
use Test::Exception;

throws_ok
    { my $unobjet; $unobjet->yoink }
    qr/Impossible d'appeler la méthode "yoink" sur un indéfini/,
    'Une méthode sur un invoquant indéfini devrait échouer';

done_testing();

La fonction exportée throws_ok() a un prototype de &$;$. Son premier argument est un bloc qui devient une fonction anonyme. Le deuxième argument est un scalaire. Le troisième argument est facultatif.

Les lecteurs attentifs ont peut-être repéré l'absence de virgule après le bloc. C'est une bizarrerie du compilateur de Perl, qui attend un espace après un bloc prototypé, pas l'opérateur virgule. C'est un inconvénient de la syntaxe de prototype. Si cela vous gêne, utilisez throws_ok() sans profiter du prototype :

 
Sélectionnez
use Test::More;
use Test::Exception;

throws_ok(
    sub { my $unobject; $unobject->yoink() },
    qr/Impossible d'appeler la méthode "yoink" sur un indéfini/,
    'Une méthode sur un invoquant indéfini devrait échouer' );

done_testing();

Une dernière bonne utilisation des prototypes est lors de la définition d'une fonction nommée personnalisée à utiliser avec sort. Ben Tilly a proposé cet exemple :

 
Sélectionnez
sub length_sort ($$)
{
    my ($left, $right) = @_;
    return length($left) <=> length($right);
}

my @sorted = sort length_sort @unsorted;

Le prototype de $$ oblige Perl à passer les paires à trier dans @_. La documentation de sort suggère que c'est un peu plus lent que l'utilisation des variables globales de paquetage $a et $b, mais l'utilisation des variables lexicales compense souvent toute perte de vitesse.

XII-D. L'équivalence méthode-fonction

Le système objet de Perl est volontairement minimal (Références liéesRéférences liées). Parce qu'une classe est un paquetage, Perl ne distingue pas entre une fonction et une méthode stockée dans un paquetage. La même commande interne, sub, déclare les deux. Perl recherchera volontiers une fonction appelée en tant que méthode. De même, vous pouvez appeler une méthode comme si c'était une fonction — pleinement qualifiée, exportée, ou comme une référence — si vous passez votre propre invoquant manuellement.

Appeler la mauvaise chose d'une mauvaise manière provoque des problèmes.

XII-D-1. Côté appelant

Imaginez une classe avec plusieurs méthodes :

 
Sélectionnez
package Commande;

use List::Util 'sum';

...

sub calculer_prix
{
    my $self = shift;
    return sum( 0, $self->get_items );
}

Étant donné un objet Commande $c, les appels suivants de cette méthode peuvent sembler équivalents :

 
Sélectionnez
my $prix = $c->calculer_prix;

# mauvais; ne pas utiliser
my $prix = Commande::calculer_prix( $c );

Bien que dans ce cas simple, ces deux appels produisent le même résultat, le second viole l'encapsulation de l'objet passant outre la recherche de méthode dans l'arbre d'héritage.

Si $c était plutôt une sous-classe ou une allomorphe (RôlesRôles) de Commande qui redéfinissait calculer_prix(), le contournement de la recherche de méthode n'appellerait pas la bonne méthode. Toute modification de la mise en œuvre de calculer_prix(), par exemple une modification de l'héritage ou une délégation par AUTOLOAD() — pourrait conduire à l'échec du code appelant.

Perl a une situation où ce comportement peut sembler nécessaire. Si vous forcez la résolution de méthode sans recherche, comment voulez-vous invoquer la référence de la méthode qui en résulte ?

 
Sélectionnez
my $meth_ref = $c->can( 'appliquer_remise' );

Il existe deux possibilités. La première consiste à ignorer la valeur de retour de la méthode can() :

 
Sélectionnez
$c->appliquer_remise if $c->can( 'appliquer_remise' );

La seconde consiste à utiliser la référence elle-même avec la syntaxe d'appel de méthode :

 
Sélectionnez
if (my $meth_ref = $o->can( 'appliquer_remise' ))
{
    $o->$meth_ref();
}

Lorsque $meth_ref contient une référence de fonction, Perl appellera cette référence avec $c comme invoquant. Cela fonctionne même avec le pragma strict, comme lors de l'appel d'une méthode avec un scalaire contenant son nom :

 
Sélectionnez
my $nom = 'appliquer_remise';
$c->$nom();

Il existe un petit inconvénient à appeler une méthode par référence ; si la structure du programme change entre le stockage de la référence et l'appel de celle-ci, la référence peut ne plus se référer à la méthode la plus appropriée. Si la classe Commande a changé de telle sorte que Commande::appliquer_remise n'est plus la bonne méthode à appeler, la référence en $meth_ref ne sera pas mise à jour.

Lorsque vous utilisez cette forme d'appel, limitez la portée des références.

XII-D-2. Côté appelé

Comme Perl ne fait aucune distinction entre les fonctions et les méthodes au moment de leur déclaration et comme il est possible (mais déconseillé) d'appeler une fonction donnée comme une fonction ou une méthode, il est possible d'écrire une fonction appelable des deux façons.

Le module CGI a ces fonctions doubles. Chacune d'entre elles doit appliquer plusieurs heuristiques pour déterminer si le premier argument est un invoquant. Cela provoque des problèmes. Il est difficile de prédire exactement quels invoquants sont potentiellement valides pour une méthode donnée, surtout quand vous pouvez avoir affaire à des sous-classes. La création d'une API que les utilisateurs ne peuvent pas facilement mal utiliser est aussi plus difficile, sans parler de votre fardeau de documentation. Que se passe-t-il lorsqu'une partie du projet utilise l'interface procédurale et une autre utilise l'interface objet ?

Si vous devez fournir des interfaces procédurale et OO distinctes pour une bibliothèque, alors créez deux API séparées.

XII-E. Déréférencement automatique

Perl peut déréférencer automatiquement certaines références en votre nom. Étant donnée une référence à un tableau dans $arrayref, vous pouvez écrire :

 
Sélectionnez
push $arrayref, qw( list of values );

Étant donnée une expression qui retourne une référence de tableau, vous pouvez faire la même chose :

 
Sélectionnez
push $maisons{$lieu}[$placards], \@nouvelles_chaussures;

Il en va de même pour les opérateurs de tableau pop, shift, unshift, splice, keys, values et each et les opérateurs de hachage keys, values, et each. Si la référence fournie n'est pas du type approprié — si elle ne déréférence pas correctement — Perl signalera une exception. Même si cela peut sembler plus dangereux que de déréférencer explicitement les références directement, c'est en fait le même comportement :

 
Sélectionnez
my $ref = sub { ... };

# signalera une exception
push  $ref, qw( liste de valeurs );

# signalera aussi une exception
push @$ref, qw( liste de valeurs );

Malheureusement, ce déréférencement automatique pose deux problèmes. Tout d'abord, il ne fonctionne que sur les variables normales. Si vous avez un tableau ou un hachage élevé au rang d'objet au moyen de la fonction bless, un hachage lié à l'aide de la fonction tie, ou un objet avec surcharge de tableau ou de hachage, Perl signalera une exception à l'exécution au lieu de déréférencer la référence.

Deuxièmement, rappelez-vous que each, keys, et values peuvent fonctionner tant sur tableaux que sur hachages. Vous ne pouvez pas examiner cette ligne de code :

 
Sélectionnez
my @items = each $ref;

... et déterminer si @items contient une liste de paires clé/valeur ou de paires indice/valeur, car vous ignorez si $ref est une référence de hachage ou de tableau. Certes, un bon choix des noms de variable aidera, mais ce code manque intrinsèquement de clarté.

Aucun de ces inconvénients ne rend cette syntaxe inutilisable en général, mais ses contours rugueux et le risque de confusion pour le lecteur la rendent moins utile qu'elle ne pourrait l'être.

XII-F. Variables liées

Là où la surcharge (SurchargeSurcharge) vous permet de personnaliser le comportement des classes et des objets pour des types spécifiques de coercition, un mécanisme appelé liaison (tying) vous permet de personnaliser le comportement des variables primitives (scalaires, tableaux, tables de hachage et descripteurs de fichiers). Toute opération que vous pouvez effectuer sur une variable liée se traduit par un appel spécifique de méthode sur un objet.

À l'origine, la commande interne tie vous autorisait à stocker des hachages et des tableaux sur l'espace disque et non en mémoire vive, ce qui permet à Perl de manipuler des volumes de données plus grands que la mémoire disponible. Le module standard Tie::File vous permet de le faire : il traite en fait les fichiers comme s'ils étaient des tableaux.

La classe à laquelle vous liez une variable doit se conformer à une interface définie pour un type spécifique de données. Lisez perldoc perltie pour un aperçu, ensuite consultez les modules standard Tie::StdScalar, Tie::StdArray et Tie::StdHash pour des détails spécifiques. Commencez par hériter d'une de ces classes, puis surchargez les méthodes spécifiques que vous devez modifier.

Collisions de noms de classe et paquetage

Si tie n'était pas assez déroutante, Tie::Scalar, Tie::Array, et Tie::Hash définissent les interfaces nécessaires pour lier scalaires, tableaux et hachages, mais Tie::StdScalar, Tie::StdArray et Tie::StdHash fournissent les implémentations par défaut.

XII-F-1. Lier des variables

Pour lier une variable :

 
Sélectionnez
use Tie::File;
tie my @file, 'Tie::File', @args;

Le premier argument est la variable à lier. Le deuxième est le nom de la classe à laquelle le lier. @args est une liste optionnelle d'arguments requis pour la fonction de liaison. Dans le cas de Tie::File, @args devrait contenir un nom de fichier valide.

Les fonctions de liaison ressemblent aux constructeurs : TIESCALAR(), TIEARRAY(), TIEHASH(), ou TIEHANDLE() respectivement pour des scalaires, tableaux et hachages et descripteurs de fichiers. Chaque fonction retourne un nouvel objet qui représente la variable liée. Tant tie que tied retournent cet objet, bien que la plupart des gens utilisent tied dans un contexte booléen.

XII-F-2. Mise en œuvre des variables liées

Pour mettre en œuvre la classe d'une variable liée, hériter d'un module standard tel que Tie::StdScalar (Tie::StdScalar ne possède pas son propre fichier .pm, alors écrivez use Tie::Scalar;), puis redéfinissez les méthodes spécifiques pour les opérations que vous souhaitez modifier. Dans le cas d'un scalaire lié, celles-ci sont probablement FETCH et STORE, éventuellement TIESCALAR(), et probablement pas DESTROY().

Voici une classe qui trace tous les accès en lecture et en écriture à un scalaire  :

 
Sélectionnez
package Tie::Scalar::Logged
{
    use Modern::Perl;

    use Tie::Scalar;
    use parent -norequire => 'Tie::StdScalar';

    sub STORE
    {
        my ($self, $valeur) = @_;
        Logger->log("Stocker <$valeur> (précédemment : [$$self])", 1);
        $$self = $valeur;
    }

    sub FETCH
    {
        my $self = shift;
        Logger->log("Récupérer <$$self>", 1);
        return $$self;
    }
}

Supposons que la méthode log() de classe Logger() prend en entrée une chaîne et le nombre d'appels de fonction à remonter dans la pile d'appels pour déterminer l'endroit dans le code que l'on désire signaler.

Dans les méthodes STORE() et FETCH(), $self fonctionne comme un scalaire lié à un objet au moyen de bless. L'affectation d'une valeur à cette référence scalaire modifie la valeur du scalaire et la lecture de celle-ci retourne sa valeur.

De même, les méthodes de Tie::StdArray et de Tie::StdHash agissent respectivement sur des références liées de tableau et hachage. Encore une fois, perldoc perltie explique les méthodes que les variables liées supportent, telles que la lecture ou l'écriture simultanée de plusieurs valeurs.

tie est amusant, non ?

L'option -norequire empêche le pragma parent de tenter de charger un fichier pour Tie::StdScalar, car ce module fait partie du fichier Tie/Scalar.pm. C'est sale, mais nécessaire.

XII-F-3. Quand utiliser les variables liées ?

Les variables liées semblent offrir des occasions amusantes de réaliser des solutions ingénieuses, mais elles peuvent produire des interfaces déroutantes. Sauf si vous avez une très bonne raison pour créer des objets se comportant comme s'ils étaient des types internes de données, évitez de créer vos propres variables liées. Elles sont souvent beaucoup plus lentes que les types de données standard.

Cela dit, les variables liées peuvent vous aider à déboguer le code délicat (utilisez le scalaire tracé de l'exemple de module Tie::Scalar::Logged donné au paragraphe ci-dessus pour vous aider à comprendre une valeur est modifiée) ou à rendre possibles certaines choses impossibles (accéder à des fichiers très volumineux sans manquer de mémoire). Les variables liées sont moins utiles en tant qu'interfaces primaires d'objets ; il est souvent trop difficile et trop contraignant d'essayer d'adapter l'ensemble de votre interface à celle soutenue par tie().

Un dernier mot d'avertissement est une triste mise en accusation de la programmation paresseuse : beaucoup de modules se plient en quatre pour empêcher l'utilisation des variables liées, ne serait-ce que par accident. C'est dommage, mais le code de la bibliothèque est parfois rapide ainsi et paresseux avec ce qu'il attend, et vous ne pouvez pas toujours y remédier.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par chromatic et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.