Perl moderne : 2014

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


précédentsommairesuivant

XI. Perl au-delà de syntaxe

Baby Perl peut résoudre beaucoup de vos problèmes, mais il ne vous mènera pas plus loin. Les programmeurs efficaces comprennent comment interagissent et se combinent les fonctionnalités de Perl. Ce Perl fluide tire parti des caractéristiques et idiomes naturels du langage. Le résultat de la pensée perliste est un code concis, puissant et utile.

XI-A. Expressions idiomatiques

Chaque langage a un ensemble commun d'expressions ou d'idiomes. La terre tourne, mais nous parlons du soleil levant ou couchant. Nous admirons les astuces de codage habiles et grinçons des dents aux ruses trop fourbes dans les programmes.

Les expressions idiomatiques de Perl sont des caractéristiques du langage ou des techniques de conception. Elles sont les maniérismes et les mécanismes qui donnent à votre code une touche perliste. Vous n'êtes pas obligés de les utiliser, mais elles contribuent aux forces de Perl.

XI-A-1. L'objet comme $self

Le système d'objet de Perl (MooseMoose) traite l'invoquant d'une méthode comme un paramètre normal. Que vous appeliez une méthode de classe ou d'instance, le premier élément de @_ est toujours l'invoquant. Par convention, la plus grande partie du code Perl utilise $class comme nom de l'invoquant d'une méthode de classe et $self pour le nom de l'invoquant objet. Cette convention est suffisamment forte pour que des extensions utiles comme MooseX::Method::Signatures supposent que vous utiliserez $self comme nom d'objet invoquant.

XI-A-2. Paramètres nommés

Perl aime beaucoup les listes. Les listes sont un élément fondamental du Perl. L'aplatissement de liste vous permet d'enchaîner plusieurs expressions pour manipuler des données de nombreuses façons afin de produire les résultats souhaités.

Alors que la simplicité du passage d'arguments de Perl (tout est aplati dans @_) est parfois trop simple, l'affectation de @_ dans un contexte de liste vous permet de déballer les paramètres nommés sous forme de paires. L'opérateur grosse virgule => (Déclarer des tables de hachageDéclarer des tables de hachage) transforme une liste ordinaire en une liste évidente de paires d'arguments :

 
Sélectionnez
preparer_coupe_glace(
    creme_fouettee => 1,
    saupoudrage    => 1,
    banane         => 0,
    creme_glacee   => 'pépites de chocolat à la menthe',
);

À l'intérieur de la fonction, le code déballe ces paramètres dans un hachage et traite ce hachage comme s'il était un seul argument :

 
Sélectionnez
sub preparer_coupe_glace
{
    my %args    = @_;
    my $dessert = get_creme_glacee( $args{creme_glacee} );

    ...
}

Hachage ou référence de hachage ?

De l'art de programmer en Perl suggère de passer plutôt des références de hachage. Cela permet à Perl d'effectuer une validation de la référence de hachage du côté de l'appelant. Autrement dit, si vous passez le mauvais nombre d'arguments, vous obtiendrez une erreur lorsque vous appelez la fonction.

Cette technique fonctionne bien avec import() (ImportationImportation) ou d'autres méthodes ; traitez autant de paramètres que vous souhaitez avant de gober tout le reste dans un hachage :

 
Sélectionnez
sub import
{
    my ($classe, %args)    = @_;
    my $paquetage_appelant = appelant();

    ...
}

XI-A-3. La transformation de Schwartz

La transformation de Schwartz est une démonstration élégante de l'utilisation omniprésente des manipulations de listes (13) en Perl, un idiome emprunté à Lisp. Supposons que vous ayez un hachage Perl qui associe les noms de vos collègues avec leurs extensions téléphoniques :

 
Sélectionnez
my %extensions =
(
    '000' => 'Damien',
    '002' => 'Wesley',
    '012' => 'Marcus',
    '042' => 'Robin',
    '088' => 'Nicolas',
);

Règles de citation de clés de hachage

La citation des clés de hachage utilisées avec la grosse virgule ne fonctionne que sur des choses qui ressemblent à des mots nus. En commençant par zéro, ces clés ressemblent à des nombres octaux. Presque tout le monde fait cette erreur.

Pour trier cette liste par ordre alphabétique des noms, vous devez trier la table de hachage par ses valeurs, et non par ses clés. Il est facile d'obtenir les valeurs triées correctement :

 
Sélectionnez
my @noms_tries = sort values %extensions;

... mais vous avez besoin d'une étape supplémentaire pour préserver l'association entre noms et extensions, d'où la transformation de Schwartz. Tout d'abord, convertissez le hachage en une liste de structures de données facile à trier — dans ce cas, des tableaux anonymes à deux éléments :

 
Sélectionnez
my @paires = map  { [ $_, $extensions{$_} ] }
            keys %extensions;

sort prend la liste des tableaux anonymes et compare leurs seconds éléments (les noms) sous forme de chaînes :

 
Sélectionnez
my @paires_triees = sort { $a->[1] cmp $b->[1] }
                         @paires;

Le bloc fourni à sort reçoit ses arguments en deux variables spéciales ayant une portée (PortéePortée) de paquetage : $a et $b. (Voir perldoc -f sort pour une analyse approfondie sur les implications de cette portée.) Le bloc sort prend ses arguments deux à la fois. Le premier devient le contenu de $a et le second le contenu de $b. Si $a devait se situer avant $b dans les résultats, le bloc doit retourner -1. Si les deux valeurs de tri ont la même position, le bloc doit retourner 0. Enfin, si $a devait venir après $b dans les résultats, le bloc doit retourner 1.

Connaissez vos données

Inverser le hachage en place fonctionnerait si personne n'avait le même nom. Cet ensemble particulier de données ne présente aucun problème, mais codez défensivement.

L'opérateur cmp effectue des comparaisons de chaînes et <=> effectue des comparaisons numériques. Une seconde opération map convertit @paires_triees en une forme plus utilisable :

 
Sélectionnez
my @extensions_formatees = map { "$_->[1], ext. $_->[0]" }
                               @paires_triees;

... et maintenant vous pouvez afficher le tout :

 
Sélectionnez
say for @extensions_formatees;

La transformation de Schwartz utilise les méthodes omniprésentes de traitement de listes de Perl pour se débarrasser des variables temporaires :

 
Sélectionnez
say for
    map  { " $_->[1], ext. $_->[0]"          }
    sort {   $a->[1] cmp   $b->[1]           }
    map  { [ $_      =>    $extensions{$_} ] }
        keys %extensions;

Lisez l'expression de droite à gauche (et de bas en haut), dans l'ordre d'évaluation. Pour chaque clé dans le hachage des extensions, faites un tableau anonyme à deux éléments contenant la clé et la valeur du hachage. Trier cette liste de tableaux anonymes par leurs seconds éléments, les valeurs du hachage. Formatez une chaîne contenant la sortie de ces tableaux triés.

Le pipeline map-sort-map de la transformation de Schwartz transforme une structure de données en une forme plus facile à trier et la retransforme ensuite en une autre forme.

Bien que cet exemple de tri soit simple, pensez au calcul d'un hachage cryptographique pour un gros fichier. La transformation de Schwartz est particulièrement utile, car elle met en cache efficacement tous les calculs coûteux en les effectuant une seule fois dans la map le plus à droite.

XI-A-4. Lire un fichier d'un seul coup

L'opérateur local est essentiel à la gestion des variables globales magiques de Perl. Vous devez comprendre la portée (PortéePortée) pour utiliser efficacement local, mais si vous le faites, vous pouvez utiliser des portées serrées et légères de manière intéressante. Par exemple, pour lire d'une seule traite des fichiers dans un scalaire en une seule expression :

 
Sélectionnez
my $file = do { local $/; <$fh> };

# ou
my $file; { local $/; $file = <$fh> };

$/ est le séparateur d'enregistrements en entrée. La localisation définit sa valeur à undef, en attente d'affectation. Comme la valeur de ce séparateur n'est pas définie, Perl lit volontiers d'un seul coup tout le contenu du descripteur de fichier. Comme un bloc do retourne la valeur de la dernière expression évaluée dans le bloc, il évalue les données lues à partir du descripteur de fichier : le contenu du fichier. À la fin de l'expression, $/ a été restaurée à son état précédent et $file contient le contenu du fichier.

Le deuxième exemple évite une deuxième copie de la chaîne contenant le contenu du fichier ; il n'est pas aussi joli, mais il utilise le moins de mémoire.

File::Slurp

Cet exemple utile est certes affolant pour les gens qui ne comprennent pas tant local que la portée. Le module File::Slurp du CPAN est une autre solution digne d'intérêt (et souvent plus rapide).

XI-A-5. Gérer Main

Perl ne nécessite aucune syntaxe spéciale pour créer des fermetures (FermeturesFermetures). Vous pouvez fermer sur une variable lexicale par inadvertance. De nombreux programmes mettent couramment en place plusieurs variables lexicales ayant une portée de fichier avant de passer le traitement à d'autres fonctions. Il est tentant d'utiliser ces variables directement, plutôt que de passer des valeurs aux fonctions et les retourner de celles-ci, d'autant plus que les programmes grossissent. Malheureusement, ces programmes peuvent en arriver à se baser sur les subtilités de ce qui se passe pendant le processus de compilation — une variable Perl, que vous pensiez avoir initialisée à une valeur spécifique peut n'être initialisée que beaucoup plus tard.

Pour éviter ce problème, enveloppez le code principal de votre programme en une seule fonction, main(). Encapsulez vos variables dans leurs portées appropriées. Puis ajoutez une seule ligne au début de votre programme, après que vous avez utilisé tous les modules et pragmas dont vous avez besoin :

 
Sélectionnez
#!/usr/bin/perl

use Modern::Perl;

...

exit main( @ARGV );

sub main {
    ...

    # successful exit
    return 0;
}

Appeler main() avant toute autre chose dans le programme vous force à être explicite sur l'initialisation et l'ordre de compilation. Appeler exit avec la valeur de retour de main() empêche tout autre code hors fonction de s'exécuter.

XI-A-6. Exécution contrôlée

La différence réelle entre un programme et un module est dans son utilisation prévue. Les utilisateurs invoquent les programmes directement, tandis que les programmes chargent des modules lorsque l'exécution a déjà commencé. Pourtant, un module et un programme sont tous les deux du code Perl. Il est facile de rendre un module exécutable. Il est de même pour faire un programme de se comporter comme un module (utile pour tester des parties d'un programme existant sans faire formellement un module). Tout ce que vous devez faire est de découvrir comment Perl a commencé à exécuter un morceau de code.

Le seul argument optionnel de la fonction caller est le nombre de contextes d'appel (RécursivitéRécursivité) à signaler. caller(0) fournit des informations sur le cadre de l'appel en cours. Pour permettre à un module de s'exécuter correctement comme un programme ou un module, mettez tout le code exécutable dans des fonctions, ajoutez une fonction main(), et écrivez une seule ligne au début du module :

 
Sélectionnez
main() unless caller(0);

S'il n'y a aucun appelant pour le module, quelqu'un l'a invoqué directement comme un programme (avec perl chemin/vers/Module.pm à la place de use Module;).

Inspection améliorée de l'appelant

Le huitième élément de la liste retourné par caller dans un contexte de liste est une valeur vraie si le contexte d'appel représente use ou require et undef autrement. Alors que c'est plus précis, peu de gens l'utilisent.

XI-A-7. Validation postfixée des paramètres

Le CPAN offre plusieurs modules qui permettent de vérifier les paramètres de vos fonctions ; Params::Validate et MooseX::Params::Validate sont deux bonnes options. La validation simple est facile même sans ces modules.

Supposons que votre fonction prend exactement deux arguments. Vous pourriez écrire :

 
Sélectionnez
use Carp 'croak';

sub toiletter_singe
{
    if (@_ != 2)
    {
        croak 'Je peux toiletter seulement deux singes !';
    }
    ...
}

... mais dans une perspective linguistique, les conséquences sont plus importantes que la vérification et méritent d'être au début de l'expression :

 
Sélectionnez
croak 'Je peux toiletter seulement deux singes !' unless @_ == 2;

Cette technique de retour précoce — surtout avec les conditions postfixées — peut simplifier le reste du code. Chaque assertion de ce type représente de fait une seule ligne dans une table de vérité.

XI-A-8. Regex en passant

De nombreux idiomes Perl s'appuient sur le fait que les expressions retournent des valeurs :

 
Sélectionnez
say my $ext_num = my $extension = 42;

Bien que ce code soit évidemment maladroit, il montre comment utiliser la valeur d'une expression dans une autre expression. Ce n'est pas une idée nouvelle ; vous avez probablement utilisé auparavant la valeur de retour d'une fonction dans une liste ou comme argument d'une autre fonction. Vous pouvez ne pas avoir réalisé ses implications.

Supposons que vous souhaitez extraire un prénom d'une combinaison prénom et nom avec une expression régulière précompilée dans $prenom_rx :

 
Sélectionnez
my ($prenom) = $nom =~ /($prenom_rx)/;

Dans un contexte de liste, une expression régulière réussie retourne une liste de toutes les captures (CapturerCapturer), et Perl attribue la première à $prenom.

Pour supprimer tous les caractères de non-mot afin de créer un nom d'utilisateur utile pour un compte système, vous pourriez écrire :

 
Sélectionnez
(my $nom_normalize = $nom) =~ tr/A-Za-z//dc;

/r en Perl 5.14

Perl 5.14 ajoute le modificateur de substitution non destructive /r, de sorte que vous pouvez écrire my $nom_normalise = $nom =~ tr/A-Za-z //dcr;.

Premièrement, attribuez la valeur de $nom à $nom_normalize. Les parenthèses modifient la priorité des opérations, afin que l'affectation se produise en premier. L'expression d'affectation retourne la variable $nom_normalize. Cette variable devient le premier opérande de l'opérateur de translittération.

Cette technique fonctionne sur d'autres opérateurs de modification en place :

 
Sélectionnez
my $age = 14;
(my $age_plus_un = $age)++;

say "J'ai $age ans, mais l'année prochaine j'aurai $age_plus_un ans";

XI-A-9. Coercitions unaires

Le système de type de Perl fait presque toujours la chose voulue lorsque vous choisissez les opérateurs corrects. Utilisez l'opérateur de concaténation et Perl traitera les deux opérandes en tant que chaînes. Utilisez l'opérateur d'addition et Perl traitera les deux opérandes comme numériques.

Occasionnellement, vous devez donner à Perl une indication sur ce que vous voulez dire avec une coercition unaire pour forcer l'évaluation d'une valeur de manière spécifique.

Pour vous assurer que Perl traite une valeur comme numérique, additionnez-la à zéro :

 
Sélectionnez
my $valeur_numerique = 0 + $valeur;

Pour vous assurer que Perl traite une valeur comme un booléen, précédez-la par une double négation :

 
Sélectionnez
my $valeur_booleenne = !! $valeur;

Pour vous assurer que Perl traite une valeur comme une chaîne, concaténez-la à une chaîne vide :

 
Sélectionnez
my $valeur_string = '' . $valeur;

Bien que la nécessité de ces coercitions soit extrêmement rare, vous devez comprendre ces idiomes si vous les rencontrez. Bien qu'il puisse sembler prudent de retirer d'une expression un +0 « inutile », il se pourrait que le programme cesse de fonctionner correctement.

XI-B. Variables globales

Perl fournit plusieurs variables super globales qui sont véritablement globales, pas limitées à la portée d'un paquetage ou d'un fichier. Malheureusement, leur disponibilité globale signifie que toute modification directe ou indirecte peut avoir des effets sur d'autres parties du programme — et elles sont très concises. Les programmeurs Perl expérimentés ont mémorisé certaines d'entre elles. Peu de gens les ont toutes mémorisées. Seules quelques-unes sont utiles. perldoc perlvar contient la liste exhaustive de ces variables.

XI-B-1. Gérer les variables super globales

Au fur et à mesure que Perl évolue, il tend à passer d'un comportement plus global à un comportement plus lexical, si bien que vous pouvez éviter beaucoup de ces variables globales. Lorsque vous ne pouvez pas les éviter, utilisez local dans la plus petite portée possible pour limiter toute modification. Vous êtes toujours sensibles à toute modification de ces variables globales par le code que vous appelez, mais vous réduisez la probabilité d'un code surprenant hors de votre portée. Comme le montre la syntaxe de lecture d'un seul coup de fichier (Lire un fichier d'un seul coupLire un fichier d'un seul coup), local est souvent la bonne approche :

 
Sélectionnez
my $file; { local $/; $file = <$fh> };

L'effet de la localisation de $/ dure seulement jusqu'à la fin du bloc. Il existe une faible chance que du code Perl s'exécute en conséquence de la lecture des lignes du descripteur de fichier et qu'il modifie la valeur de $/ dans le bloc do. Un descripteur de fichier lié par la fonction tie (Variables liéesVariables liées) est l'une des rares possibilités.

Tous les cas d'utilisation de variables super globales ne sont pas si faciles à protéger, mais cela fonctionne souvent.

D'autres fois, vous avez besoin de lire la valeur d'une super globale et espérez qu'aucune autre ligne de code ne l'a modifiée. L'interception des exceptions avec un bloc eval est sensible à au moins une condition de course où l'appel de la méthode DESTROY() sur des variables lexicales sorties de leur portée peut réinitialiser $@ :

 
Sélectionnez
local $@;

eval { ... };

if (my $exception = $@) { ... }

Copiez $@ immédiatement après l'interception d'une exception pour préserver son contenu. Voir aussi Try::Tiny à la place (Dangers des exceptionsDangers des exceptions).

XI-B-2. Noms anglais

Le module standard English fournit des noms verbeux pour les variables super globales à ponctuation lourde. Importez-les dans un espace de noms ainsi :

 
Sélectionnez
use English '-no_match_vars';

Cela vous permet d'utiliser les noms verbeux documentés dans perldoc perlvar dans la portée de ce pragma.

Trois variables super globales liées aux expressions régulières ($&, $` et $') entraînent une baisse de performance globale pour toutes les expressions régulières dans un programme. Si vous oubliez l'import de -no_match_vars, votre programme sera pénalisé même si vous n'utilisez pas explicitement ces variables. Les programmes Perl modernes peuvent utiliser la variable @- à leur place.

XI-B-3. Variables super globales utiles

La majorité des programmes Perl modernes peuvent fonctionner en utilisant seulement quelques variables super globales. Vous allez probablement rencontrer seulement quelques-unes de ces variables dans les programmes réels.

  • $/ (ou $INPUT_RECORD_SEPARATOR du pragma English) est une chaîne de zéro, un ou plusieurs caractères qui indique la fin d'un enregistrement lorsque vous lisez une ligne d'entrée à la fois. Par défaut, c'est la séquence de caractère de saut de ligne spécifique à votre plate-forme. Si vous donnez à cette variable une valeur indéfinie, Perl tentera de lire l’intégralité du fichier en mémoire. Si vous initialisez cette valeur avec la référence d'un nombre entier, Perl essayera de lire autant de bytes par enregistrement (alors, méfiez-vous des problèmes Unicode). Si vous définissez cette valeur à une chaîne vide (''), Perl lira un paragraphe à la fois, où un paragraphe est un morceau de texte suivi par un nombre arbitraire de sauts de ligne.
  • $. ($INPUT_LINE_NUMBER) contient le nombre d'enregistrements lus à partir du descripteur de fichier le plus récemment ouvert. Vous pouvez lire le contenu de cette variable, mais lui affecter une valeur n'a aucun effet. La localisation de cette variable localisera le descripteur de fichier auquel elle se réfère.
  • $| ($OUTPUT_AUTOFLUSH) détermine si Perl écrira immédiatement dans le descripteur de fichier actuellement sélectionné ou seulement lorsque la mémoire tampon de Perl est pleine. La sortie sans tampon est utile lors de l'écriture dans un pipe, un socket ou un terminal qui ne devrait pas se bloquer en attente d'une entrée. Cette variable convertira en valeurs booléennes toutes les valeurs qui lui sont affectées.
  • @ARGV contient les arguments de ligne de commande passés au programme.
  • $! ($ERRNO) est une dualvar (DualvarsDualvars) qui contient le résultat de l'appel système le plus récent. Dans le contexte numérique, cela correspond à la valeur errno de C, où toute autre valeur que zéro indique une erreur. Dans le contexte de chaîne, elle retourne la chaîne de caractères décrivant l'erreur système. Localisez cette variable avant de faire un appel système (implicitement ou explicitement) pour éviter d'écraser la valeur appropriée par un autre fragment de code ailleurs. La mécanique interne de Perl fait parfois des appels système, de sorte que la valeur de cette variable peut changer à votre insu. Copiez-la immédiatement après avoir provoqué un appel système pour obtenir des résultats corrects.
  • $" ($LIST_SEPARATOR) est une chaîne utilisée pour séparer les éléments de tableau et de liste interpolés dans une chaîne.
  • %+ contient les captures nommées des correspondances d'expressions régulières réussies (Captures nomméesCaptures nommées).
  • $@ ($EVAL_ERROR) contient la valeur de l'exception la plus récente (Intercepter les exceptionsIntercepter les exceptions).
  • $0 ($PROGRAM_NAME) contient le nom du programme en cours d'exécution. Sur certaines plates-formes Unix, vous pouvez modifier cette valeur pour changer le nom du programme tel qu'il apparaît à d'autres programmes sur le système, tels que ps ou top.
  • $$ ($PID) contient l'identifiant du processus de l'instance en cours d'exécution du programme, tel que compris par le système d'exploitation. Cela variera entre les programmes ayant fait un fork() et peut varier entre les threads d'exécution du même programme.
  • INC détient une liste des chemins d'accès dans lequel Perl recherchera les fichiers à charger avec use ou require. Voir perldoc -f require pour les autres éléments que ce tableau peut contenir.
  • %SIG mappe les signaux de bas niveau de Perl et de l'OS vers des références de fonctions utilisées pour traiter ces signaux. Bloquez l'interruption standard Ctrl-C en traitant le signal INT, par exemple. Voir perldoc perlipc pour plus d'informations sur les signaux et traitements des signaux.

XI-B-4. Remplacer les variables super globales

Les entrées-sorties et les exceptions sont les pires coupables d'actions à distance. Utilisez Try::Tiny (Dangers des exceptionsDangers des exceptions) pour vous isoler de la sémantique délicate de gestion adéquate d'exceptions. localisez et copiez la valeur de $! pour éviter les comportements étranges quand Perl effectue des appels système implicites. Utilisez IO::File et ses méthodes sur descripteurs de fichiers lexicaux (Variables spéciales de gestion des fichiersVariables spéciales de gestion des fichiers) pour limiter les changements globaux non désirés au comportement des entrées-sorties.


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 © 2015 Developpez.com.