it-swarm.com.de

Was genau macht Perls "Segen"?

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

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

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

136
user47145

Im Allgemeinen ordnet bless ein Objekt einer Klasse zu.

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

Wenn Sie nun unter $object Eine Methode aufrufen, weiß Perl, welches Paket nach der Methode durchsucht werden soll.

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

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

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

EDIT: Siehe kixx 's gut Antwort für ein bisschen mehr Details.

138
Gordon Wilson

bless ordnet eine Referenz einem Paket zu.

Es spielt keine Rolle, worauf sich der Verweis bezieht, es kann sich um einen Hash (häufigster Fall), ein Array (weniger häufig) oder einen Skalar handeln (normalerweise bedeutet dies Inside-Out-Objekt =), einem regulären Ausdruck, einer Subroutine oder TYPEGLOB (siehe das Buch Object Oriented Perl: Ein umfassender Leitfaden zu Konzepten und Programmiertechniken von Damian Conway für nützliche Beispiele) oder sogar einen Verweis auf eine Datei oder ein Verzeichnis behandeln (am wenigsten häufig).

Der Effekt von bless- ist, dass Sie eine spezielle Syntax auf die gesegnete Referenz anwenden können.

Wenn beispielsweise eine gesegnete Referenz in $obj Gespeichert ist (verknüpft mit bless mit dem Paket "Class"), ruft $obj->foo(@args) eine Unterroutine foo auf. und übergeben Sie als erstes Argument die Referenz $obj gefolgt von den restlichen Argumenten (@args). Das Unterprogramm sollte im Paket "Class" definiert werden. Wenn das Paket "Class" kein Unterprogramm foo enthält, wird eine Liste anderer Pakete (aus dem Array @ISA Im Paket "Class") durchsucht und das erste Unterprogramm foo found wird aufgerufen.

77
kixx

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

9
chaos

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. Es wird empfohlen, die aus zwei Argumenten bestehende Form des Segens zu verwenden.

Beispiel:

bless REF, CLASSNAME
bless REF

Rückgabewert

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

Beispiel:

Der folgende Beispielcode zeigt die grundlegende Verwendung. 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;
}
6
linuxtestside

Ich werde hier eine Antwort geben, da die hier nicht ganz auf mich geklickt haben.

Perls Segensfunktion ordnet alle Verweise auf alle Funktionen in einem Paket zu.

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 zu schreiben) und gibt eine Hash-Tabelle mit diesen Eigenschaften zurück, oder, wenn Sie das neue Schlüsselwort vergessen haben, diese Gibt den gesamten globalen Kontext zurück (z. B. Fenster im Browser oder global in NodeJS).

Perl hat weder "this" noch "new" noch "class", aber es kann trotzdem eine Funktion haben, die sich ähnlich verhält. Wir haben weder einen Konstrukteur noch einen Prototyp, können aber nach Belieben neue Tiere erstellen und deren individuelle Eigenschaften ä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 wollen eine Funktion performSound, die den eigenen Klang des Tieres druckt.

Eine Möglichkeit, dies zu tun, besteht darin, jedem einzelnen Tier beizubringen, wie man seinen Klang macht. Dies bedeutet, dass jede Katze ihre eigene Dublettenfunktion 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'}();
})->();

Das ist schlecht, weil performSound jedes Mal, wenn ein Tier konstruiert wird, als völlig neues Funktionsobjekt abgelegt wird. 10000 Tiere bedeuten 10000 PerformSounds. Wir möchten, dass eine einzige Funktion performSound verwendet wird, die von allen Tieren verwendet wird, die ihren eigenen Sound nachschlagen und ausdrucken.

(() => {
    '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 JavaScript-Operator ist nicht optional. Ohne ihn 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 für jedes Tier eine Funktion haben, die den eigenen Klang des Tieres nachschlägt, anstatt ihn bei der Konstruktion festzukodieren.

Mit Blessing können wir ein Paket als Prototyp von Objekten verwenden. Auf diese Weise erkennt das Objekt das "Paket", auf das es "verwiesen" wird, und die Funktionen im Paket können wiederum auf die spezifischen Instanzen zugreifen, 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 kein "this", "class" oder "new". Durch das Segnen eines Objekts in ein Paket erhält dieses Objekt einen Verweis auf das Paket. Wenn es Funktionen im Paket aufruft, werden deren Argumente um 1 Feld versetzt und das erste Argument ($ _ [0] oder shift) entspricht Javascript ist "das". 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 Sie für jede "Klasse" ein eigenes Paket benötigen, 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, um neue Funktionen hinzuzufügen und Funktionen im laufenden Betrieb zu entfernen.

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 mit JavaScript möglich ist. Perl ist nicht so flexibel (zumindest nicht integriert, Sie müssen es erfinden oder von anderen Modulen beziehen), und Ihre Laufzeit-Expressivität wird beeinträchtigt. Wenn man es "segne" nennt, tut es auch nicht viel Gutes.

Was wir machen wollen:

Etwa so, aber rekursiv an die Prototypenkarte binden und implizit an den Prototyp gebunden sein, anstatt dies explizit tun zu müssen.

Hier ist ein naiver Versuch: Das Problem ist, dass "call" nicht weiß, "wie es genannt wurde". Es kann sich also auch um eine universelle Perl-Funktion "objectInvokeMethod (object, method)" handeln, 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 Nice eval magic, um es zu tun, 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 hilfreich.

4
Dmitry

Ich folge diesem Gedanken, um die Entwicklung objektorientierter Perl zu leiten.

Zuordnen von Datenstrukturreferenzen zu einer Klasse. In Anbetracht dessen, wie Perl die Vererbungsstruktur erstellt (in einer Art Baum), ist es einfach, das Objektmodell zu nutzen, um Objekte für die Komposition zu erstellen.

Für diese Assoziation haben wir Objekt genannt, um immer im Hinterkopf zu behalten, dass der interne Zustand des Objekts und das Klassenverhalten voneinander getrennt sind. Sie können jeden Datenverweis segnen/zulassen, um ein beliebiges Paket-/Klassenverhalten zu verwenden. Da das Paket "den emotionalen" Zustand des Objekts verstehen kann.

1
Steven Koch