Was genau macht Perls "Segen"?


141

Ich verstehe, dass man das Schlüsselwort "segne" in Perl in der "neuen" Methode einer Klasse verwendet:

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

Aber was genau macht "segne" mit dieser Hash-Referenz?


2
Siehe "Bless My Referents" aus dem Jahr 1999. Sieht ziemlich detailliert aus. (Der Perl manuelle Eintrag hat leider nicht viel zu sagen.)
Jon Skeet

Antworten:


142

blessOrdnet ein Objekt im Allgemeinen einer Klasse zu.

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

Wenn Sie nun eine Methode aufrufen $object, weiß Perl, welches Paket nach der Methode gesucht werden soll.

Wenn das zweite Argument wie in Ihrem Beispiel weggelassen wird, wird das aktuelle Paket / die aktuelle Klasse verwendet.

Aus Gründen der Klarheit könnte Ihr Beispiel wie folgt geschrieben sein:

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

EDIT: Weitere Informationen finden Sie in der guten Antwort von kixx .


78

bless ordnet einem Paket eine Referenz zu.

Es spielt keine Rolle, worauf sich die Referenz bezieht, es kann sich um einen Hash (häufigster Fall), ein Array (nicht so häufig), einen Skalar (normalerweise zeigt dies ein Inside-Out-Objekt an ) oder einen regulären Ausdruck handeln , Unterprogramm oder TYPEGLOB ( nützliche Beispiele finden Sie im Buch Objektorientiertes Perl: Ein umfassender Leitfaden zu Konzepten und Programmiertechniken von Damian Conway ) oder sogar einen Verweis auf ein Datei- oder Verzeichnishandle (am wenigsten verbreiteter Fall).

Der Effekt blessbesteht darin, dass Sie eine spezielle Syntax auf die gesegnete Referenz anwenden können.

Wenn beispielsweise eine gesegnete Referenz in gespeichert ist $obj(verbunden blessmit dem Paket "Klasse"), $obj->foo(@args)ruft sie eine Unterroutine auf foound übergibt als erstes Argument die Referenz, $objgefolgt von den restlichen Argumenten ( @args). Das Unterprogramm sollte im Paket "Klasse" definiert sein. Wenn das fooPaket "Klasse" keine Unterroutine enthält , wird eine Liste anderer Pakete (aus dem Array @ISAim Paket "Klasse" entnommen ) durchsucht und die erste foogefundene Unterroutine aufgerufen.


16
Ihre ursprüngliche Aussage ist falsch. Ja, segne nimmt eine Referenz als erstes Argument, aber es ist die Referenzvariable, die gesegnet wird, nicht die Referenz selbst. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; segne $ h, "Somepackage"; $ j = \% h; print $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
Die Erklärung von Kixx ist umfassend. Wir sollten uns nicht darum kümmern, dass der Konverter theoretische Kleinigkeiten auswählt.
Seliger Geek

19
@Blessed Geek, es sind keine theoretischen Kleinigkeiten. Der Unterschied hat praktische Anwendungen.
Ikegami

3
Der alte perlfoundation.org-Link für "Inside-Out-Objekt" befindet sich jetzt bestenfalls hinter einer Login-Wand. Der Link zum Original von Archive.org ist hier .
Ruffin

2
Vielleicht wird dies anstelle des defekten Links dienen, den @harmic kommentiert hat: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Kurzversion: Es markiert den Hash als an den aktuellen Paketnamespace angehängt (damit dieses Paket seine Klassenimplementierung bereitstellt).


7

Diese Funktion teilt der von REF referenzierten Entität mit, dass es sich nun um ein Objekt im CLASSNAME-Paket handelt oder um das aktuelle Paket, wenn CLASSNAME weggelassen wird. Die Verwendung der Segenform mit zwei Argumenten wird empfohlen.

Beispiel :

bless REF, CLASSNAME
bless REF

Rückgabewert

Diese Funktion gibt den Verweis auf ein in CLASSNAME gesegnetes Objekt zurück.

Beispiel :

Im Folgenden finden Sie den Beispielcode, der die grundlegende Verwendung zeigt. Die Objektreferenz wird erstellt, indem eine Referenz auf die Klasse des Pakets gesegnet wird.

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

Ich werde hier eine Antwort geben, da die hier nicht ganz für mich geklickt haben.

Perls Segensfunktion verknüpft jeden Verweis auf alle Funktionen innerhalb eines Pakets.

Warum brauchen wir das?

Beginnen wir mit einem Beispiel 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
})();

Lassen Sie uns nun das Klassenkonstrukt entfernen und darauf verzichten:

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

Die Funktion verwendet eine Hash-Tabelle mit ungeordneten Eigenschaften (da es 2016 keinen Sinn macht, Eigenschaften in einer bestimmten Reihenfolge in dynamischen Sprachen schreiben zu müssen) und gibt eine Hash-Tabelle mit diesen Eigenschaften zurück, oder wenn Sie vergessen haben, das neue Schlüsselwort einzugeben gibt den gesamten globalen Kontext zurück (z. B. Fenster im Browser oder global in nodejs).

Perl hat weder "dies" noch "neu" noch "Klasse", kann aber dennoch eine Funktion haben, die sich ähnlich verhält. Wir werden weder einen Konstruktor noch einen Prototyp haben, aber wir werden in der Lage sein, nach Belieben neue Tiere zu erstellen und ihre individuellen Eigenschaften zu ändern.

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

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

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

Jetzt haben wir ein Problem: Was ist, wenn wir möchten, dass das Tier die Geräusche selbst ausführt, anstatt dass wir drucken, was ihre Stimme ist? Das heißt, wir möchten eine Funktion performSound, die den eigenen Klang des Tieres druckt.

Eine Möglichkeit, dies zu tun, besteht darin, jedem einzelnen Tier beizubringen, wie es seinen Klang erzeugt. Dies bedeutet, dass jede Katze ihre eigene Duplikatfunktion hat, um Sound auszuführen.

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

Dies ist schlecht, da performSound jedes Mal, wenn ein Tier konstruiert wird, als völlig neues Funktionsobjekt gesetzt wird. 10000 Tiere bedeuten 10000 performSounds. Wir möchten eine einzige Funktion performSound haben, die von allen Tieren verwendet wird, die ihren eigenen Sound nachschlagen und ihn drucken.

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

Hier hört die Parallele zu Perl auf.

Der neue Operator von JavaScript ist nicht optional. Ohne diesen Operator beschädigt "this" innerhalb der Objektmethoden den globalen Bereich:

(() => {
    // '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.

})();

Wir möchten eine Funktion für jedes Tier haben, die den eigenen Klang dieses Tieres nachschlägt, anstatt ihn beim Bau fest zu codieren.

Durch den Segen können wir ein Paket als Prototyp von Objekten verwenden. Auf diese Weise kennt das Objekt das "Paket", auf das es "referenziert" ist, und kann wiederum die Funktionen im Paket dazu bringen, in die spezifischen Instanzen zu "greifen", die vom Konstruktor dieses "Paketobjekts" erstellt wurden:

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

Zusammenfassung / TL; DR :

Perl hat weder "dies", "Klasse" noch "neu". Wenn Sie ein Objekt für ein Paket segnen, erhält dieses Objekt einen Verweis auf das Paket. Wenn es Funktionen im Paket aufruft, werden ihre Argumente um 1 Slot versetzt, und das erste Argument ($ _ [0] oder Shift) entspricht Javascript ist "dies". Im Gegenzug können Sie das Prototypmodell von JavaScript etwas simulieren.

Leider ist es (nach meinem Verständnis) unmöglich, zur Laufzeit "neue Klassen" zu erstellen, da jede "Klasse" ein eigenes Paket haben muss, während Sie in Javascript überhaupt keine Pakete als "neues" Schlüsselwort benötigen erstellt eine anonyme Hashmap, die Sie zur Laufzeit als Paket verwenden können, zu der Sie im laufenden Betrieb neue Funktionen hinzufügen und Funktionen entfernen können.

Es gibt einige Perl-Bibliotheken, die ihre eigenen Methoden entwickeln, um diese Einschränkung der Ausdruckskraft zu überwinden, wie z. B. Moose.

Warum die Verwirrung? ::

Wegen Paketen. Unsere Intuition sagt uns, dass wir das Objekt an eine Hashmap binden sollen, die seinen Prototyp enthält. Auf diese Weise können wir zur Laufzeit "Pakete" erstellen, wie dies JavaScript kann. Perl verfügt nicht über eine solche Flexibilität (zumindest nicht eingebaut, Sie müssen es erfinden oder von anderen Modulen beziehen), und Ihre Laufzeit-Ausdruckskraft wird wiederum beeinträchtigt. Es "Segen" zu nennen, tut ihm auch nicht viel Gutes.

Was wir tun wollen :

So etwas, aber die Bindung an die Prototypkarte ist rekursiv und implizit an den Prototyp gebunden, anstatt dies explizit tun zu müssen.

Hier ist ein naiver Versuch: Das Problem ist, dass "call" nicht weiß, wie es heißt ", also kann es auch eine universelle Perl-Funktion" objectInvokeMethod (object, method) "sein, die prüft, ob das Objekt die Methode hat oder sein Prototyp hat es oder sein Prototyp hat es, bis es das Ende erreicht und es findet oder nicht (prototypische Vererbung). Perl hat eine schöne Eval-Magie, aber ich lasse das für etwas, das ich später versuchen kann.

Jedenfalls ist hier die Idee:

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

Wie auch immer, hoffentlich findet jemand diesen Beitrag nützlich.


Es ist nicht unmöglich, zur Laufzeit neue Klassen zu erstellen. my $o = bless {}, $anything;wird ein Objekt in die $anythingKlasse segnen . In ähnlicher Weise {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};wird eine Methode mit dem Namen 'somesub' in der Klasse mit dem Namen 'somesub' erstellt $anything. Dies ist alles zur Laufzeit möglich. "Möglich" macht es jedoch nicht zu einer großartigen Praxis, im täglichen Code zu arbeiten. Es ist jedoch nützlich beim Erstellen von Objektüberlagerungssystemen wie Moose oder Moo.
DavidO

Interessant, Sie sagen also, ich kann einen Referenten in eine Klasse segnen, deren Name zur Laufzeit festgelegt wird. Scheint interessant und macht meinen unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimeAnspruch ungültig . Ich denke, meine Besorgnis hat sich letztendlich darauf beschränkt, dass es wesentlich weniger intuitiv ist, das Paketsystem zur Laufzeit zu manipulieren / zu überprüfen, aber bisher habe ich nichts gezeigt, was es von Natur aus nicht kann. Das Paketsystem scheint alle Tools zu unterstützen, die zum Hinzufügen / Entfernen / Prüfen / Ändern zur Laufzeit erforderlich sind.
Dmitry

Das ist richtig; Sie können die Symboltabelle von Perl programmgesteuert bearbeiten und daher zur Laufzeit die Pakete von Perl und die Mitglieder eines Pakets bearbeiten, auch ohne "package Foo" irgendwo deklariert zu haben. Inspektion und Bearbeitung von Symboltabellen zur Laufzeit, AUTOLOAD-Semantik, Unterprogrammattribute, Verknüpfung von Variablen mit Klassen ... es gibt viele Möglichkeiten, unter die Haube zu kommen. Einige von ihnen sind nützlich für die automatische Generierung von APIs, Validierungswerkzeugen und APIs für die automatische Dokumentation. Wir können nicht alle Anwendungsfälle vorhersagen. Sich in den Fuß zu schießen, ist auch ein mögliches Ergebnis solcher Tricks.
DavidO

4

Zusammen mit einer Reihe von guten Antworten zeichnet sich eine bless-ed-Referenz besonders dadurch aus, dass die SV dafür ein zusätzliches FLAGS( OBJECT) und einSTASH

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'

Drucke, bei denen dieselben (und hierfür irrelevanten) Teile unterdrückt werden

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

Damit ist bekannt, dass 1) es ein Objekt ist 2) zu welchem ​​Paket es gehört, und dies informiert über seine Verwendung.

Wenn beispielsweise eine Dereferenzierung auf diese Variable auftritt ( $obj->name), wird im Paket (oder in der Hierarchie) ein Sub mit diesem Namen gesucht, das Objekt wird als erstes Argument übergeben usw.


1

I Diesem Gedanken folgend, um die Entwicklung objektorientierter Perl zu leiten.

Bless ordnet eine Datenstrukturreferenz einer Klasse zu. Angesichts der Art und Weise, wie Perl die Vererbungsstruktur (in einer Art Baum) erstellt, ist es einfach, das Objektmodell zu nutzen, um Objekte für die Komposition zu erstellen.

Für diese Assoziation haben wir Objekt genannt, um zu entwickeln, immer daran zu denken, dass der interne Zustand des Objekts und das Klassenverhalten getrennt sind. Und Sie können jede Datenreferenz segnen / zulassen, um jedes Paket- / Klassenverhalten zu verwenden. Da das Paket "den emotionalen" Zustand des Objekts verstehen kann.


Hier finden Sie die gleichen Ankündigungen dazu, wie Perl mit Namespaces von Paketen funktioniert und wie mit in Ihrem Namespace registrierten Status. Weil dies Pragmas wie use namespace :: clean existieren. Aber versuchen Sie, die Dinge einfacher zu machen.
Steven Koch

-9

Wenn Sie beispielsweise sicher sein können, dass ein Bug-Objekt ein gesegneter Hash sein wird, können Sie (endlich!) Den fehlenden Code in der Bug :: print_me-Methode eingeben:

 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";
 }

Wenn nun die print_me-Methode über eine Referenz auf einen Hash aufgerufen wird, der in die Bug-Klasse aufgenommen wurde, extrahiert die Variable $ self die Referenz, die als erstes Argument übergeben wurde, und die print-Anweisungen greifen dann auf die verschiedenen Einträge des gesegneten Hash zu.


@darch Aus welcher Quelle wurde diese Antwort plagiiert?
Anderson Green
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.