Que fait exactement la «bénédiction» de Perl?


142

Je comprends que l'on utilise le mot-clé "bless" en Perl dans la méthode "new" d'une classe:

sub new {
    my $self = bless { };
    return $self;
}    

Mais que fait exactement "bénir" à cette référence de hachage?


2
Voir "Bénis mes référents" de 1999. Ça a l'air assez détaillé. (L' entrée du manuel Perl n'a pas grand-chose à dire là-dessus, malheureusement.)
Jon Skeet

Réponses:


143

En général, blessassocie un objet à une classe.

package MyClass;
my $object = { };
bless $object, "MyClass";

Désormais, lorsque vous invoquez une méthode $object, Perl sait dans quel package rechercher la méthode.

Si le deuxième argument est omis, comme dans votre exemple, le package / classe actuel est utilisé.

Dans un souci de clarté, votre exemple pourrait s'écrire comme suit:

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 

EDIT: Voir la bonne réponse de kixx pour un peu plus de détails.


79

bless associe une référence à un package.

Peu importe à quoi la référence est, elle peut être à un hachage (cas le plus courant), à un tableau (pas si courant), à un scalaire (généralement cela indique un objet à l'envers ), à une expression régulière , sous-programme ou TYPEGLOB (voir le livre Perl orienté objet: un guide complet des concepts et des techniques de programmation par Damian Conway pour des exemples utiles) ou même une référence à un descripteur de fichier ou de répertoire (cas le moins courant).

L'effet bless a est qu'il vous permet d'appliquer une syntaxe spéciale à la référence bénie.

Par exemple, si une référence bénie est stockée dans $obj(associée blessau package "Class"), alors $obj->foo(@args)appellera un sous foo- programme et passera comme premier argument la référence $objsuivie du reste des arguments ( @args). Le sous-programme doit être défini dans le package "Class". S'il n'y a pas de sous-programme foodans le package "Class", une liste des autres packages (pris dans le tableau @ISAdu package "Class") sera recherchée et le premier sous-programme footrouvé sera appelé.


16
Votre déclaration initiale est incorrecte. Oui, bless prend une référence comme premier argument, mais c'est la variable référente qui est bénie, pas la référence elle-même. $ perl -le 'sous Somepackage :: foo {42}; % h = (); $ h = \% h; bénissez $ h, "Somepackage"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
L'explication de Kixx est complète. Nous ne devrions pas nous soucier de la sélection du convertisseur sur les minuties théoriques.
Blessed Geek

19
@Blessed Geek, ce ne sont pas des minuties théoriques. La différence a des applications pratiques.
ikegami

3
L'ancien lien perlfoundation.org pour "objet à l'envers" est, au mieux, derrière un mur de connexion maintenant. Le lien Archive.org de l'original est ici .
ruffin le

2
Peut-être que cela servira à la place du lien brisé @harmic commenté sur: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Version courte: il marque ce hachage comme attaché à l'espace de noms du package actuel (afin que ce package fournisse son implémentation de classe).


7

Cette fonction indique à l'entité référencée par REF qu'il s'agit désormais d'un objet dans le package CLASSNAME, ou du package actuel si CLASSNAME est omis. L'utilisation de la forme à deux arguments de bénir est recommandée.

Exemple :

bless REF, CLASSNAME
bless REF

Valeur de retour

Cette fonction renvoie la référence à un objet béni dans CLASSNAME.

Exemple :

Voici l'exemple de code montrant son utilisation de base, la référence d'objet est créée en bénissant une référence à la classe du package -

#!/usr/bin/perl

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # Print all the values just for clarification.
    print "First Name is $self->{_firstName}\n";
    print "Last Name is $self->{_lastName}\n";
    print "SSN is $self->{_ssn}\n";
    bless $self, $class;
    return $self;
}

4

Je vais fournir une réponse ici car ceux ici n'ont pas tout à fait cliqué pour moi.

La fonction bless de Perl associe toute référence à toutes les fonctions à l'intérieur d'un paquet.

Pourquoi en aurions-nous besoin?

Commençons par exprimer un exemple en JavaScript:

(() => {
    'use strict';

    class Animal {
        constructor(args) {
            this.name = args.name;
            this.sound = args.sound;
        }
    }

    /* [WRONG] (global scope corruption)
     * var animal = Animal({
     *     'name': 'Jeff',
     *     'sound': 'bark'
     * }); 
     * console.log(animal.name + ', ' + animal.sound); // seems good
     * console.log(window.name); // my window's name is Jeff?
     */

    // new is important!
    var animal = new Animal(
        'name': 'Jeff',   
        'sound': 'bark'
    );

    console.log(animal.name + ', ' + animal.sound); // still fine.
    console.log(window.name); // undefined
})();

Supprimons maintenant la construction de classe et faisons-nous en sans:

(() => {
    'use strict';

    var Animal = function(args) {
        this.name = args.name;
        this.sound = args.sound;
        return this; // implicit context hashmap
    };

    // the "new" causes the Animal to be unbound from global context, and 
    // rebinds it to an empty hash map before being constructed. The state is
    // now bound to animal, not the global scope.
    var animal = new Animal({
        'name': 'Jeff',
        'sound': 'bark'
    });
    console.log(animal.sound);    
})();

La fonction prend une table de hachage de propriétés non ordonnées (car cela n'a aucun sens d'avoir à écrire des propriétés dans un ordre spécifique dans des langages dynamiques en 2016) et renvoie une table de hachage avec ces propriétés, ou si vous avez oublié de mettre le nouveau mot-clé, il retournera tout le contexte global (par exemple, fenêtre dans le navigateur ou global dans nodejs).

Perl n'a ni "this", ni "new" ni "class", mais il peut toujours avoir une fonction qui se comporte de la même manière. Nous n'aurons pas de constructeur ni de prototype, mais nous pourrons créer de nouveaux animaux à volonté et modifier leurs propriétés individuelles.

# self contained scope 
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();

Maintenant, nous avons un problème: que se passe-t-il si nous voulons que l'animal exécute les sons par lui-même au lieu d'imprimer sa voix. Autrement dit, nous voulons une fonction performSound qui imprime le propre son de l'animal.

Une façon de faire est d'apprendre à chaque animal à faire son son. Cela signifie que chaque chat a sa propre fonction de duplication à exécuter.

# self contained scope 
(sub {
    my $Animal = (sub {
        $name = $_[0]{'name'};
        $sound = $_[0]{'sound'};

        return {
            'name' => $name,
            'sound' => $sound,
            'performSound' => sub {
                print $sound . "\n";
            }
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    $animal->{'performSound'}();
})->();

Ceci est mauvais car performSound est mis en tant qu'objet de fonction complètement nouveau à chaque fois qu'un animal est construit. 10000 animaux signifie 10000 performSounds. Nous voulons avoir une seule fonction performSound qui soit utilisée par tous les animaux qui recherchent leur propre son et l'impriment.

(() => {
    'use strict';

    /* a function that creates an Animal constructor which can be used to create animals */
    var Animal = (() => {
        /* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
        var InnerAnimal = function(args) {
            this.name = args.name;
            this.sound = args.sound;
        };
        /* defined once and all animals use the same single function call */
        InnerAnimal.prototype.performSound = function() {
            console.log(this.name);
        };

        return InnerAnimal;
    })();

    /* we're gonna create an animal with arguments in different order
       because we want to be edgy. */
    var animal = new Animal({
        'sound': 'bark',
        'name': 'Jeff'
    });
    animal.performSound(); // Jeff
})();

C'est ici que s'arrête un peu le parallèle avec Perl.

Le nouvel opérateur de JavaScript n'est pas facultatif, sans lui, "this" à l'intérieur des méthodes d'objet corrompt la portée globale:

(() => {
    // 'use strict'; // uncommenting this prevents corruption and raises an error instead.

    var Person = function() {
        this.name = "Sam";
    };
//    var wrong = Person(); // oops! we have overwritten window.name or global.main.
//    console.log(window.name); // my window's name is Sam?
    var correct = new Person; // person's name is actually stored in the person now.

})();

Nous voulons avoir une fonction pour chaque animal qui recherche le son de cet animal plutôt que de le coder en dur lors de la construction.

La bénédiction nous permet d'utiliser un package comme prototype d'objets. De cette façon, l'objet est conscient du "package" auquel il est "référencé", et peut à son tour avoir les fonctions du package "atteindre" les instances spécifiques qui ont été créées à partir du constructeur de cet "objet package":

package Animal;
sub new {
    my $packageRef = $_[0];
    my $name = $_[1]->{'name'};
    my $sound = $_[1]->{'sound'};

    my $this = {
        'name' => $name,
        'sound' => $sound
    };   

    bless($this, $packageRef);
    return $this;
}

# all animals use the same performSound to look up their sound.
sub performSound {
    my $this = shift;
    my $sound = $this->{'sound'};
    print $sound . "\n";
}

package main;
my $animal = Animal->new({
    'name' => 'Cat',
    'sound' => 'meow'
});
$animal->performSound();

Résumé / TL; DR :

Perl n'a ni "ceci", ni "classe", ni "nouveau". bénir un objet dans un package donne à cet objet une référence au package, et quand il appelle des fonctions dans le package, leurs arguments seront décalés de 1 slot, et le premier argument ($ _ [0] ou shift) sera équivalent à "ceci" de javascript. À son tour, vous pouvez simuler un peu le modèle prototype de JavaScript.

Malheureusement, il est impossible (à ma connaissance) de créer de "nouvelles classes" à l'exécution, car vous avez besoin que chaque "classe" ait son propre package, alors qu'en javascript, vous n'avez pas du tout besoin de packages, en tant que mot-clé "nouveau" constitue un hashmap anonyme que vous pouvez utiliser comme package au moment de l'exécution auquel vous pouvez ajouter de nouvelles fonctions et supprimer des fonctions à la volée.

Certaines bibliothèques Perl créent leurs propres moyens de surmonter cette limitation d'expressivité, comme Moose.

Pourquoi cette confusion? :

À cause des paquets. Notre intuition nous dit de lier l'objet à un hashmap contenant son prototype. Cela nous permet de créer des "packages" au moment de l'exécution comme le peut JavaScript. Perl n'a pas une telle flexibilité (du moins pas intégré, vous devez l'inventer ou l'obtenir à partir d'autres modules), et à son tour votre expressivité d'exécution est entravée. L'appeler "bénir" ne le fait pas non plus.

Ce que nous voulons faire :

Quelque chose comme ça, mais avoir une liaison récursive avec la carte prototype, et être implicitement lié au prototype plutôt que d'avoir à le faire explicitement.

En voici une tentative naïve: le problème est que "call" ne sait pas "comment on l'a appelé", donc cela peut aussi bien être une fonction perl universelle "objectInvokeMethod (object, method)" qui vérifie si l'objet a la méthode , ou son prototype l'a, ou son prototype l'a, jusqu'à ce qu'il atteigne la fin et le trouve ou non (héritage prototypique). Perl a une belle magie eval pour le faire, mais je laisserai cela pour quelque chose que je pourrai essayer de faire plus tard.

Bref, voici l'idée:

(sub {

    my $Animal = (sub {
        my $AnimalPrototype = {
            'performSound' => sub {
                return $_[0]->{'sound'};
            }
        };

        my $call = sub {
            my $this = $_[0];
            my $proc = $_[1];

            if (exists $this->{$proc}) {
                return $this->{$proc}->();
            } else {
                return $this->{prototype}->{$proc}->($this, $proc);
            }
        };

        return sub {
            my $name = $_[0]->{name};
            my $sound = $_[0]->{sound};

            my $this = { 
                'this' => $this,
                'name' => $name,
                'sound' => $sound,
                'prototype' => $AnimalPrototype,
                'call' => $call                
            };
        };
    })->();

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound'=> 'bark'
    });
    print($animal->{call}($animal, 'performSound'));
})->();

Quoi qu'il en soit, j'espère que quelqu'un trouvera cet article utile.


Il n'est pas impossible de créer de nouvelles classes au moment de l'exécution. my $o = bless {}, $anything;bénira un objet dans la $anythingclasse. De même, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};créera une méthode nommée 'somesub' dans la classe nommée dans $anything. Tout cela est possible au moment de l'exécution. «Possible», cependant, n'en fait pas une excellente pratique à utiliser dans le code de tous les jours. Mais il est utile dans la construction de systèmes de superposition d'objets tels que Moose ou Moo.
DavidO

intéressant, donc vous dites que je peux bénir un référent dans une classe dont le nom est décidé au moment de l'exécution. Cela semble intéressant et annule ma unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimeréclamation. Je suppose que mon inquiétude s'est finalement résumée au fait qu'il est beaucoup moins intuitif de manipuler / introspecter le système de paquets au moment de l'exécution, mais jusqu'à présent, j'ai échoué à montrer tout ce qu'il ne peut pas faire par nature. Le système de package semble prendre en charge tous les outils nécessaires pour s'ajouter / supprimer / inspecter / se modifier au moment de l'exécution.
Dmitry

C'est correct; vous pouvez manipuler la table des symboles de Perl par programme, et donc manipuler les paquets de Perl et les membres d'un paquet à l'exécution, même sans avoir déclaré "package Foo" nulle part. Inspection et manipulation de table de symboles à l'exécution, sémantique AUTOLOAD, attributs de sous-programmes, liaison de variables à des classes ... il existe de nombreuses façons de passer sous le capot. Certains d'entre eux sont utiles pour la génération automatique d'API, d'outils de validation, d'API de documentation automatique; nous ne pouvons pas prédire tous les cas d'utilisation. Se tirer une balle dans le pied est également le résultat possible d'une telle supercherie.
DavidO

4

En plus d'un certain nombre de bonnes réponses, ce qui distingue spécifiquement une blessréférence -ed est que le SV for prend un FLAGS( OBJECT) et unSTASH

perl -MDevel::Peek -wE'
    package Pack  { sub func { return { a=>1 } } }; 
    package Class { sub new  { return bless { A=>10 } } }; 
    $vp  = Pack::func(); print Dump $vp;   say"---"; 
    $obj = Class->new;   print Dump $obj'

Impressions, avec les mêmes parties (et non pertinentes pour cela) supprimées

SV = IV (0x12d5530) à 0x12d5540
  REFCNT = 1
  DRAPEAUX = (ROK)
  RV = 0x12a5a68
  SV = PVHV (0x12ab980) à 0x12a5a68
    REFCNT = 1
    DRAPEAUX = (SHAREKEYS)
    ...
      SV = IV (0x12a5ce0) à 0x12a5cf0
      REFCNT = 1
      DRAPEAUX = (IOK, pIOK)
      IV = 1
---
SV = IV (0x12cb8b8) à 0x12cb8c8
  REFCNT = 1
  DRAPEAUX = (PADMY, ROK)
  RV = 0x12c26b0
  SV = PVHV (0x12aba00) à 0x12c26b0
    REFCNT = 1
    DRAPEAUX = (OBJET, SHAREKEYS)
    STASH = 0x12d5300 "Classe"
    ...
      SV = IV (0x12c26b8) à 0x12c26c8
      REFCNT = 1
      DRAPEAUX = (IOK, pIOK)
      IV = 10

Avec cela, on sait que 1) c'est un objet 2) à quel paquet il appartient, et cela informe son utilisation.

Par exemple, lorsque le déréférencement sur cette variable est rencontré ( $obj->name), un sous avec ce nom est recherché dans le package (ou la hiérarchie), l'objet est passé comme premier argument, etc.


1

I Suite à cette réflexion pour guider le développement orienté objet Perl.

Bless associez toute référence de structure de données à une classe. Etant donné la façon dont Perl crée la structure d'héritage (dans une sorte d'arbre), il est facile de profiter du modèle objet pour créer des objets pour la composition.

Pour cette association que nous avons appelée objet, pour développer, gardez toujours à l'esprit que l'état interne de l'objet et les comportements de classe sont séparés. Et vous pouvez bénir / autoriser n'importe quelle référence de données à utiliser n'importe quel comportement de package / classe. Depuis l'emballage peut comprendre "l'état émotionnel" de l'objet.


Voici les mêmes annonces concernant la façon dont Perl fonctionne avec les espaces de noms de paquets et comment fonctionne avec les états enregistrés dans votre espace de noms. Parce que cela existe des pragmas comme use namespace :: clean. Mais essayez de garder les choses plus simples possibles.
Steven Koch

-9

Par exemple, si vous pouvez être sûr que tout objet Bug sera un hachage béni, vous pouvez (enfin!) Remplir le code manquant dans la méthode Bug :: print_me:

 package Bug;
 sub print_me
 {
     my ($self) = @_;
     print "ID: $self->{id}\n";
     print "$self->{descr}\n";
     print "(Note: problem is fatal)\n" if $self->{type} eq "fatal";
 }

Maintenant, chaque fois que la méthode print_me est appelée via une référence à un hachage qui a été béni dans la classe Bug, la variable $ self extrait la référence qui a été passée comme premier argument, puis les instructions print accèdent aux différentes entrées du hachage béni.


@darch De quelle source cette réponse a-t-elle été plagiée?
Anderson Green
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.