Cosa fa esattamente la "benedizione" di Perl?


142

Capisco che uno usa la parola chiave "benedica" in Perl all'interno del metodo "nuovo" di una classe:

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

Ma cosa sta facendo esattamente "benedici" per quel riferimento all'hash?


2
Vedi "Bless My Referents" del 1999. Sembra piuttosto dettagliato. (L' entrata manuale del Perl non ha molto da dire, purtroppo.)
Jon Skeet,

Risposte:


143

In generale, blessassocia un oggetto a una classe.

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

Ora, quando invochi un metodo $object, Perl sa quale pacchetto cercare il metodo.

Se il secondo argomento viene omesso, come nell'esempio, viene utilizzato il pacchetto / classe corrente.

Per motivi di chiarezza, il tuo esempio potrebbe essere scritto come segue:

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

EDIT: vedi la buona risposta di Kixx per un po 'più di dettagli.


79

bless associa un riferimento a un pacchetto.

Non importa quale sia il riferimento, può essere un hash (caso più comune), un array (non così comune), uno scalare (di solito questo indica un oggetto dentro-fuori ), un'espressione regolare , subroutine o TYPEGLOB (vedere il libro Perl orientato agli oggetti: una guida completa ai concetti e alle tecniche di programmazione di Damian Conway per esempi utili) o persino un riferimento a un handle di file o directory (caso meno comune).

L'effetto blessha che ti permette di applicare una sintassi speciale al riferimento benedetto.

Ad esempio, se un riferimento benedetto è memorizzato in $obj(associato blessal pacchetto "Class"), allora $obj->foo(@args)chiamerà una subroutine fooe passerà come primo argomento il riferimento $objseguito dal resto degli argomenti ( @args). La subroutine deve essere definita nel pacchetto "Class". Se non è presente alcuna subroutine foonel pacchetto "Class", @ISAverrà cercato un elenco di altri pacchetti (preso dall'array nel pacchetto "Class") e fooverrà chiamata la prima subroutine trovata.


16
La tua dichiarazione iniziale non è corretta. Sì, la benedizione prende un riferimento come primo argomento, ma è la variabile referente ad essere benedetta, non il riferimento stesso. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; benedici $ h, "Somepackage"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
La spiegazione di Kixx è completa. Non dovremmo preoccuparci della scelta del convertitore di minuzie teoriche.
Beato Geek, il

19
@Blessed Geek, non sono minuzie teoriche. La differenza ha applicazioni pratiche.
ikegami,

3
Il vecchio link perlfoundation.org per "oggetto dentro-fuori" è, nella migliore delle ipotesi, dietro un muro di accesso ora. Il link Archive.org dell'originale è qui .
ruffin,

2
Forse questo servirà al posto del @harmic link non funzionante commentato: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Versione breve: contrassegna tale hash come allegato allo spazio dei nomi del pacchetto corrente (in modo che quel pacchetto fornisca la sua implementazione di classe).


7

Questa funzione indica all'entità a cui fa riferimento REF che ora è un oggetto nel pacchetto CLASSNAME o il pacchetto corrente se CLASSNAME viene omesso. Si raccomanda l'uso della forma a due argomenti della benedizione.

Esempio :

bless REF, CLASSNAME
bless REF

Valore di ritorno

Questa funzione restituisce il riferimento a un oggetto benedetto in CLASSNAME.

Esempio :

Di seguito è riportato il codice di esempio che mostra il suo utilizzo di base, il riferimento all'oggetto viene creato benedendo un riferimento alla classe del pacchetto -

#!/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

Fornirò una risposta qui poiché quelli qui non mi hanno cliccato.

La funzione benedica di Perl associa qualsiasi riferimento a tutte le funzioni all'interno di un pacchetto.

Perché dovremmo averne bisogno?

Cominciamo esprimendo un esempio in 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
})();

Ora consente di eliminare il costrutto di classe e di farne a meno:

(() => {
    '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 funzione accetta una tabella hash di proprietà non ordinate (dal momento che non ha senso scrivere proprietà in un ordine specifico in linguaggi dinamici nel 2016) e restituisce una tabella hash con quelle proprietà, o se hai dimenticato di inserire la nuova parola chiave, restituirà l'intero contesto globale (ad es. finestra nel browser o globale in nodejs).

Perl non ha "questo" né "nuovo" né "classe", ma può comunque avere una funzione che si comporta in modo simile. Non avremo un costruttore né un prototipo, ma saremo in grado di creare nuovi animali a volontà e modificare le loro proprietà individuali.

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

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

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

Ora, abbiamo un problema: che cosa succede se vogliamo che l'animale esegua i suoni da soli invece di stampare la loro voce. Cioè, vogliamo una funzione performSound che stampa il suono dell'animale.

Un modo per farlo è insegnare a ogni singolo animale come fare il suono. Ciò significa che ogni gatto ha la sua funzione duplicata per eseguire SoundSound.

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

Ciò è negativo perché performSound viene inserito come un oggetto funzione completamente nuovo ogni volta che viene costruito un animale. 10000 animali significa 10000 performSounds. Vogliamo avere una sola funzione performSound che viene utilizzata da tutti gli animali che cerca il proprio suono e lo stampa.

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

Qui è dove si ferma il parallelo a Perl kinda.

Il nuovo operatore JavaScript non è facoltativo, senza di esso, "questo" all'interno dei metodi degli oggetti corrompe l'ambito 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.

})();

Vogliamo avere una funzione per ogni Animale che cerca il suono di quell'animale piuttosto che codificarlo alla costruzione.

La benedizione ci consente di utilizzare un pacchetto come prototipo di oggetti. In questo modo, l'oggetto è a conoscenza del "pacchetto" a cui "fa riferimento" e, a sua volta, può avere le funzioni nel pacchetto "raggiungere" le istanze specifiche che sono state create dal costruttore di tale "oggetto pacchetto":

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

Riepilogo / TL; DR :

Perl non ha "questo", "classe", né "nuovo". la benedizione di un oggetto in un pacchetto fornisce a tale oggetto un riferimento al pacchetto e quando chiama le funzioni nel pacchetto, i loro argomenti saranno compensati di 1 slot e il primo argomento ($ _ [0] o shift) sarà equivalente a "questo" di javascript. A sua volta, è possibile simulare in qualche modo il modello prototipo di JavaScript.

Sfortunatamente, per la mia comprensione, è impossibile (a mio avviso) creare "nuove classi" in fase di esecuzione, poiché è necessario che ogni "classe" abbia il proprio pacchetto, mentre in javascript non è necessario alcun pacchetto, come "nuova" parola chiave crea una hashmap anonima da utilizzare come pacchetto in fase di esecuzione a cui è possibile aggiungere nuove funzioni e rimuovere funzioni al volo.

Ci sono alcune librerie Perl che creano i propri modi per colmare questa limitazione espressiva, come Moose.

Perché la confusione? :

Per via dei pacchetti. La nostra intuizione ci dice di legare l'oggetto a una hashmap contenente il suo 'prototipo. Questo ci consente di creare "pacchetti" in fase di esecuzione come JavaScript può. Perl non ha una tale flessibilità (almeno non integrata, è necessario inventarla o ottenerla da altri moduli) e, a sua volta, l'espressività del runtime viene ostacolata. Chiamarlo "benedica" non fa neanche molto favore.

Cosa vogliamo fare :

Qualcosa del genere, ma è vincolante per la mappa del prototipo ricorsivo ed è implicitamente legato al prototipo piuttosto che doverlo fare esplicitamente.

Ecco un tentativo ingenuo: il problema è che "call" non sa "come lo ha chiamato", quindi potrebbe anche essere una funzione perl universale "objectInvokeMethod (oggetto, metodo)" che controlla se l'oggetto ha il metodo , o il suo prototipo ce l'ha, o il suo prototipo ce l'ha, fino a quando non raggiunge la fine e la trova o meno (eredità prototipica). Perl ha una bella magia eval da fare, ma lo lascerò per qualcosa che posso provare a fare in seguito.

Comunque ecco l'idea:

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

Speriamo comunque che qualcuno troverà utile questo post.


Non è impossibile creare nuove classi in fase di esecuzione. my $o = bless {}, $anything;benedirà un oggetto nella $anythingclasse. Allo stesso modo, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};creerà un metodo chiamato 'somesub' nella classe chiamata in $anything. Tutto ciò è possibile in fase di esecuzione. "Possibile", tuttavia, non lo rende una buona pratica esercitare nel codice di tutti i giorni. Ma è utile nella costruzione di sistemi di sovrapposizione di oggetti come Moose o Moo.
DavidO,

interessante, quindi stai dicendo che posso benedire un referente in una classe il cui nome è deciso in fase di esecuzione. Sembra interessante e annulla la mia unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimerichiesta. Immagino che la mia preoccupazione alla fine si sia ridotta a essere significativamente meno intuitivo per manipolare / introspettare il sistema di pacchetti in fase di esecuzione, ma finora non ho mostrato nulla che intrinsecamente non potesse fare. Il sistema di pacchetti sembra supportare tutti gli strumenti necessari per aggiungere / rimuovere / ispezionare / modificare se stesso in fase di esecuzione.
Dmitry

Questo è corretto; puoi manipolare la tabella dei simboli di Perl in modo programmatico, e quindi puoi manipolare i pacchetti di Perl e i membri di un pacchetto in fase di esecuzione, anche senza aver dichiarato "pacchetto Foo" ovunque. Ispezione e manipolazione della tabella dei simboli in fase di esecuzione, semantica di AUTOLOAD, attributi di subroutine, legame di variabili a classi ... ci sono molti modi per andare sotto il cofano. Alcuni di essi sono utili per la generazione automatica di API, strumenti di validazione, API di autodocumentazione; non possiamo prevedere tutti i casi d'uso. Spararsi al piede è anche un possibile risultato di tale inganno.
DavidO

4

Insieme a una serie di buone risposte, ciò che distingue in modo specifico un blessriferimento -ed è che SV per esso raccoglie un ulteriore FLAGS( OBJECT) e 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'

Stampa, con le stesse (e irrilevanti per questo) parti soppresse

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

Con ciò si sa che 1) è un oggetto 2) a quale pacchetto appartiene, e questo ne informa il suo utilizzo.

Ad esempio, quando viene rilevata la dereferenziazione su quella variabile ( $obj->name), viene cercato un sottotitolo con quel nome nel pacchetto (o gerarchia), l'oggetto viene passato come primo argomento, ecc.


1

Seguendo questo pensiero per guidare lo sviluppo Perl orientato agli oggetti.

Bless associa qualsiasi riferimento alla struttura dei dati a una classe. Dato come Perl crea la struttura di ereditarietà (in una specie di albero) è facile sfruttare il modello a oggetti per creare oggetti per la composizione.

Per questa associazione che abbiamo chiamato oggetto, per sviluppare bisogna sempre tenere presente che lo stato interno dell'oggetto e i comportamenti di classe sono separati. E puoi benedire / consentire a qualsiasi riferimento ai dati di utilizzare qualsiasi comportamento di pacchetto / classe. Dal momento che il pacchetto può comprendere lo stato "emotivo" dell'oggetto.


Ecco gli stessi annunci di come Perl funziona con gli spazi dei nomi dei pacchetti e di come funziona con gli stati registrati nel tuo spazio dei nomi. Perché esistono pragmi come use namespace :: clean. Ma cerca di rendere le cose più semplici possibili.
Steven Koch,

-9

Ad esempio, se puoi essere sicuro che qualsiasi oggetto Bug sarà un hash benedetto, puoi (finalmente!) Compilare il codice mancante nel metodo 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";
 }

Ora, ogni volta che il metodo print_me viene chiamato tramite un riferimento a qualsiasi hash che è stato benedetto nella classe Bug, la variabile $ self estrae il riferimento che è stato passato come primo argomento e quindi le istruzioni print accedono alle varie voci dell'hash benedetto.


@darch Da quale fonte è stata plagiata questa risposta?
Anderson Green,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.