web-dev-qa-db-fra.com

Que fait exactement la "bénédiction" de Perl?

Je crois comprendre que l’on utilise le mot-clé "bless" en Perl dans la méthode "nouvelle" d'une classe:

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

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

136
user47145

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

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

Maintenant, lorsque vous appelez une méthode sur $object, Perl sait quel paquet chercher.

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

Par souci de clarté, votre exemple pourrait être écrit comme suit:

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

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

138
Gordon Wilson

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

Peu importe la référence, il peut s'agir d'un hachage (cas le plus courant), d'un tableau (peu commun), d'un scalaire (généralement, il s'agit d'un objet à l'envers =), à une expression régulière, à un sous-programme ou à TYPEGLOB (voir le livre Perl orienté objet: un guide complet des concepts et techniques de programmation de Damian Conway) pour des exemples utiles) ou même une référence à un fichier ou à un répertoire gérer (cas moins commun).

L’effet de blessing est qu’il vous permet d’appliquer une syntaxe spéciale à la référence privilégiée.

Par exemple, si une référence bénite est stockée dans $obj (Associée par bless au paquet "Class"), alors $obj->foo(@args) appellera un sous-programme foo et passez comme premier argument la référence $obj suivie du reste des arguments (@args). Le sous-programme doit être défini dans le package "Classe". S'il n'y a pas de sous-programme foo dans le package "Class", une liste d'autres packages (pris dans le tableau @ISA Du package "Class") sera recherchée et le premier sous-programme foo trouvé sera appelé.

77
kixx

Version courte: il marque ce hachage comme étant attaché à l'espace de nom du paquet actuel (pour que ce paquet fournisse son implémentation de classe).

9
chaos

Cette fonction indique à l'entité référencée par REF qu'il s'agit désormais d'un objet du package CLASSNAME ou du package actuel si CLASSNAME est omis. L'utilisation de la forme de bénédiction à deux arguments 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 illustrant son utilisation de base. La référence de l'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;
}
6
linuxtestside

Je vais fournir une réponse ici car ceux-ci ne m'ont pas tout à fait cliqué.

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

Pourquoi aurions-nous besoin de cela?

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
})();

Maintenant, supprimons la construction de la classe et faisons-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 (puisqu'il n'est pas logique de devoir écrire des propriétés dans un ordre spécifique dans les 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é, retournera tout le contexte global (par exemple, fenêtre dans le navigateur ou global dans nodejs).

Perl n'a ni "ce" ni "nouveau" ni "classe", mais il peut toujours avoir une fonction qui se comporte de la même manière. Nous n'aurons ni constructeur ni 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 produise les sons lui-même au lieu d’imprimer sa voix? C'est-à-dire que nous voulons une fonction performSound qui imprime le son de l'animal.

Une façon de le faire est d’enseigner à chaque animal comment le faire. Cela signifie que chaque chat a sa propre fonction de duplication à effectuer.

# 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'}();
})->();

Cela est grave car performSound est mis comme un 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 est utilisée par tous les animaux pour rechercher leur propre son et l’imprimer.

(() => {
    '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
})();

Voici où le parallèle à Perl s'arrête un peu.

Le nouvel opérateur JavaScript n'est pas facultatif. Sans lui, "cet" méthodes d'objet internes altèrent 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 propre de cet animal plutôt que de le coder en dur lors de la construction.

Bénédiction nous permet d'utiliser un paquet comme prototype d'objets. De cette façon, l'objet est conscient du "package" auquel il est "référencé", et les fonctions du package peuvent à leur tour "pénétrer" dans les instances spécifiques créées à partir du constructeur de cet "objet de 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", "classe", ni "nouveau". La bénédiction d'un objet sur un paquet donne à cet objet une référence au paquet. Lorsqu'il appelle des fonctions dans le paquet, leurs arguments sont décalés d'un créneau et le premier argument ($ _ [0] ou shift) est équivalent à javascript est "ceci". À son tour, vous pouvez simuler un peu le modèle de prototype de JavaScript.

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

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

Pourquoi la 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 permet JavaScript. Perl n’a pas une telle flexibilité (du moins pas intégré, vous devez l’inventer ou le récupérer à partir d’autres modules), ce qui nuit à l’expressivité de votre exécution. L'appeler "bénir" ne fait pas beaucoup de faveurs non plus.

Ce que nous voulons faire:

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

Voici une tentative naïve: le problème est que "call" ne sait pas "comment il a appelé", il peut donc aussi bien s'agir d'une fonction universelle Perl "objectInvokeMethod (objet, méthode)" 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 la magie eval de Nice pour le faire mais je le laisserai pour une chose que je pourrai faire plus tard.

En tout cas 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 ce post utile.

4
Dmitry

I Suite à cette pensée pour guider le développement orienté objet Perl.

Bless associe toute référence de structure de données à une classe. Étant donné que Perl crée la structure d'héritage (dans une sorte d'arborescence), il est facile de tirer parti du modèle objet pour créer des objets pour la composition.

Pour cette association, nous avons appelé objet, 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 toute référence de données à utiliser les comportements de package/classe. Depuis l'emballage peut comprendre l'état "émotionnel" de l'objet.

1
Steven Koch