Perl'in “kutsamaları” tam olarak ne yapar?


142

Bir sınıfın "yeni" yöntemi içinde Perl "bless" anahtar kelimesini kullandığını anlıyorum:

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

Peki "kutsama" bu karma referans için tam olarak ne yapıyor?


2
1999'dan itibaren "Referanslarımı Koru" bölümüne bakın . Oldukça ayrıntılı görünüyor. ( Perl manuel girişinin maalesef söyleyecek çok şeyi yok.)
Jon Skeet

Yanıtlar:


143

Genel olarak, blessbir nesneyi bir sınıfla ilişkilendirir.

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

Şimdi bir yöntemi çağırdığınızda $object, Perl yöntemi hangi paketi arayacağını biliyor.

Örneğinizde olduğu gibi ikinci argüman atlanırsa, geçerli paket / sınıf kullanılır.

Anlaşılır olması için, örneğin aşağıdaki gibi yazılabilir:

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

DÜZENLEME: Biraz daha ayrıntı için kixx'in iyi cevabına bakınız .


79

bless referansı bir paketle ilişkilendirir.

Referansın ne olduğu önemli değil, bir karma (en yaygın durum), bir dizi (çok yaygın değil), bir skalere (genellikle bu bir iç-dış nesneyi gösterir ), normal bir ifadeye olabilir , altyordam veya TYPEGLOB ( Nesne Odaklı Perl: Damian Conway'in Kavramlar ve Programlama Teknikleri için Kapsamlı Bir Kılavuz adlı kitabına bakın ) ve hatta bir dosya veya dizin tanıtıcısına başvuru (en az yaygın durum).

Etkisi bless, kutsanmış referansa özel sözdizimi uygulamanıza izin vermesidir.

Örneğin, kutsanmış bir referans depolanırsa $obj( bless"Class" paketi ile ilişkilendirilir ), $obj->foo(@args)bir alt rutini çağırır foove ilk argüman olarak referansı ve $objardından argümanların ( @args) geri kalanını iletir . Altyordam "Sınıf" paketinde tanımlanmalıdır. foo"Class" paketinde alt rutin yoksa, diğer paketlerin bir listesi ( @ISA"Class" paketindeki diziden alınır ) aranır ve bulunan ilk alt rutin fooçağrılır.


16
İlk ifadeniz yanlış. Evet, bless bir referansı ilk argümanı olarak alır, ancak mübarek olan referans değişkendir, referansın kendisi değildir. $ perl -le 'sub Somepackage :: foo {42}; % H = (); $ h = \% h; $ h korusun, "Somepackage"; $ j = \% sa; print $ j-> UNIVERSAL :: can ("foo") -> ()
dönüştürücü42

1
Kixx'in açıklaması kapsamlı. Dönüştürücünün teorik minutiaları seçmesiyle uğraşmamalıyız.
Blessed Geek

19
@ Mübarek Geek, Teorik minutiae değil. Farkın pratik uygulamaları vardır.
Ikegami

3
"İçten dışa nesne" için eski perlfoundation.org bağlantısı en iyi ihtimalle şu anda bir giriş duvarının arkasında. Orijinalin Archive.org bağlantısı burada .
ruffin

2
Belki de bu kırık link yerine hizmet edecek @harmic yorumladı: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Kısa sürüm: karmayı geçerli paket ad alanına eklenmiş olarak işaretler (böylece bu paket sınıf uygulamasını sağlar).


7

Bu işlev, REF tarafından başvurulan varlığa artık CLASSNAME paketindeki bir nesne veya CLASSNAME belirtilmezse geçerli paket olduğunu bildirir. Kutsama iki argüman biçiminin kullanılması tavsiye edilir.

Örnek :

bless REF, CLASSNAME
bless REF

Geri dönüş değeri

Bu işlev, CLASSNAME ile kutsanmış bir nesneye başvuruyu döndürür.

Örnek :

Temel kullanımını gösteren örnek kod aşağıdadır, nesne referansı, paketin sınıfına bir referans kutsamakla oluşturulur.

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

Buraya bir cevap vereceğim, çünkü buradakiler benim için tıklamamıştı.

Perl'in kutsama işlevi, herhangi bir referansı bir paket içindeki tüm işlevlerle ilişkilendirir.

Neden buna ihtiyacımız var?

JavaScript'te bir örnek belirterek başlayalım:

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

Şimdi sınıf yapısını kaldırıp onsuz yapalım:

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

İşlev, sıralanmamış özelliklerin bir karma tablosunu alır (2016'da dinamik dillerde belirli bir sırayla özellik yazmak zorunda kalmaz) ve bu özelliklere sahip bir karma tablo döndürür veya yeni anahtar kelimeyi koymayı unuttuysanız, tüm global bağlamı döndürür (örn. tarayıcıdaki pencere veya global olarak nodejs).

Perl'de "bu", "yeni" veya "sınıf" yoktur, ancak yine de benzer şekilde davranan bir işlevi olabilir. Bir kurucumuz ya da bir prototipimiz olmayacak, ancak istedikleri zaman yeni hayvanlar yaratabilecek ve bireysel özelliklerini değiştirebileceğiz.

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

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

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

Şimdi bir sorunumuz var: Ya hayvanın seslerini basmak yerine sesleri kendileri çalmasını istiyorsak. Yani, bir fonksiyonun gerçekleştirilmesini istiyoruzHayvanın kendi sesini basan ses.

Bunu yapmanın bir yolu, her bir hayvana sesin nasıl yapılacağını öğretmektir. Bu, her Cat'in gerçekleştirmesi için kendi çoğaltma işlevine sahip olduğu anlamına gelir.

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

Bu kötüdür çünkü performSound, bir hayvan her inşa edildiğinde tamamen yeni bir fonksiyon nesnesi olarak konur. 10000 hayvan 10000 performans anlamına gelir. Kendi seslerini arayan ve basan tüm hayvanlar tarafından kullanılan tek bir işleve sahip olmak istiyoruz.

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

İşte Perl'e paralel olarak burada durur.

JavaScript'in yeni işleci isteğe bağlı değildir, onsuz "nesne" içindeki nesne yöntemleri global kapsamı bozar:

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

})();

Her hayvan için, yapımda kodlamak yerine o hayvanın kendi sesini arayan bir fonksiyona sahip olmak istiyoruz.

Blessing, bir paketi nesnelerin prototipi olarak kullanmamızı sağlar. Bu şekilde, nesne "başvuruda bulunulan" paketin farkındadır ve paketteki işlevlerin "paket nesnesinin" yapıcısından oluşturulan belirli örneklere "ulaşmasını" sağlayabilir:

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

Özet / TL; DR :

Perl'in "bu", "sınıf" veya "yeni" yoktur. bir nesneyi bir pakete kutsamak, o nesneye pakete bir başvuru verir ve paketteki işlevleri çağırdığında, argümanları 1 yuva ile dengelenir ve ilk argüman ($ _ [0] veya shift) javascript "bu" dır. Buna karşılık, JavaScript'in prototip modelini biraz taklit edebilirsiniz.

Ne yazık ki, her bir "sınıfın" kendi paketine sahip olması gerektiğinden, çalışma zamanında "yeni sınıflar" oluşturmayı imkansız kılarken, javascript'te "yeni" anahtar kelime olarak hiçbir pakete ihtiyacınız yoktur çalışma zamanında yeni işlevler ekleyebileceğiniz ve işlevleri anında kaldırabileceğiniz bir paket olarak kullanmanız için anonim bir hashmap oluşturur.

Moose gibi ifadede bu sınırlamayı köprülemek için kendi yollarını yaratan bazı Perl kütüphaneleri var.

Neden karışıklık? :

Paketler yüzünden. Sezgimiz bize nesneyi 'prototipini içeren bir hashmap'a bağlamamızı söylüyor. Bu, çalışma zamanında JavaScript'in yapabileceği gibi "paketler" oluşturmamızı sağlar. Perl böyle bir esnekliğe sahip değildir (en azından yerleşik değildir, onu icat etmeniz veya diğer modüllerden almanız gerekir) ve sırayla çalışma zamanı ifadeniz engellenir. Ona “kutsama” deme, pek de iyilik yapmaz.

Ne yapmak istiyoruz :

Bunun gibi bir şey, ancak prototip haritasına özyinelemeli bağlanma var ve bunu açıkça yapmak yerine prototipe dolaylı olarak bağlı olun.

İşte naif bir girişim: sorun "çağrı" bilmiyor "ne denir", bu yüzden de nesnenin yöntemi olup olmadığını kontrol evrensel bir perl işlevi "objectInvokeMethod (nesne, yöntem)" olabilir veya prototipine sahip olur ya da prototipine kadar, sonuna ulaşıp bulana kadar (prototipik miras). Perl'in bunu yapmak için güzel bir değerlendirme büyüsü var ama bunu daha sonra deneyebileceğim bir şey için bırakacağım.

Her neyse, burada fikir:

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

Neyse umarım birisi bu yazıyı faydalı bulacaktır.


Çalışma zamanında yeni sınıflar oluşturmak imkansız değildir. sınıfa my $o = bless {}, $anything;bir nesneyi kutsasın $anything. Benzer şekilde, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};adlı sınıfta 'somesub' adlı bir yöntem oluşturur $anything. Bunların hepsi çalışma zamanında mümkündür. Ancak "Mümkün", günlük kodda kullanılmasını pek iyi bir uygulama haline getirmiyor. Ancak Moose veya Moo gibi nesne bindirme sistemlerinin oluşturulmasında yararlıdır.
DavidO

ilginç, yani adı çalışma zamanında karar verilen bir sınıfa bir mübarek kutsama. İlginç görünüyor ve iddiamı geçersiz unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimekılıyor. Endişem sonunda sonuçta paket sistemini manipüle etmek / introspect etmek için önemli ölçüde daha az sezgisel olmakla kaygılıydı, ancak şimdiye kadar doğal olarak yapamayacağı bir şey gösteremedim. Paket sistemi, çalışma zamanında kendini eklemek / kaldırmak / incelemek / değiştirmek için gerekli tüm araçları destekliyor gibi görünüyor.
Dmitry

Doğru; Perl'in sembol tablosunu programlı olarak değiştirebilir ve bu nedenle Perl paketlerini ve bir paketin üyelerini çalışma zamanında, herhangi bir yerde "paket Foo" olarak bildirmeden bile değiştirebilirsiniz. Çalışma zamanında sembol tablosu incelemesi ve manipülasyonu, AUTOLOAD semantiği, altyordam nitelikleri, değişkenlerin sınıflara bağlanması ... başlık altına girmenin birçok yolu vardır. Bunlardan bazıları otomatik olarak üretilen API'ler, doğrulama araçları, otomatik dokümantasyon API'ları için yararlıdır; tüm kullanım durumlarını tahmin edemeyiz. Ayakta kendini vurmak da bu tür hilelerin olası bir sonucudur.
DavidO

4

Bir dizi iyi cevabın yanı sıra, a bless-ed referansını özel olarak ayıran şey, bunun SV için bir ek FLAGS( OBJECT) ve birSTASH

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'

Aynı (ve bununla alakasız) parçaların bastırılmasıyla yazdırılır

SV = IV (0x12d5530), 0x12d5540'ta
  REFCNT = 1
  BAYRAK = (ROK)
  RV = 0x12a5a68
  SV = 0x12a5a68'de PVHV (0x12ab980)
    REFCNT = 1
    BAYRAK = (SHAREKEYS)
    ...
      SV = IV (0x12a5ce0), 0x12a5cf0 konumunda
      REFCNT = 1
      BAYRAK = (IOK, pIOK)
      IV = 1
---
SV = IV (0x12cb8b8), 0x12cb8c8'de
  REFCNT = 1
  BAYRAK = (PADMY, ROK)
  RV = 0x12c26b0
  SV = 0x12c26b0'da PVHV (0x12aba00)
    REFCNT = 1
    BAYRAK = (NESNE, SHAREKEYS)
    STASH = 0x12d5300 "Sınıf"
    ...
      SV = IV (0x12c26b8), 0x12c26c8'de
      REFCNT = 1
      BAYRAK = (IOK, pIOK)
      IV = 10

Bununla, 1) bir nesnenin 2) hangi pakete ait olduğu bilinir ve bu onun kullanımını bilgilendirir.

Örneğin, bu değişken üzerinde kayıt silme ile karşılaşıldığında ( $obj->name), pakette (veya hiyerarşide) bu ada sahip bir alt öğe aranır, nesne ilk argüman olarak geçirilir.


1

I Bu düşünceyi takiben gelişim nesnesine yönelik Perl'e rehberlik etmek.

Bless, herhangi bir veri yapısı referansını bir sınıfla ilişkilendirir. Perl'in kalıtım yapısını (bir tür ağaçta) nasıl yarattığı göz önüne alındığında, kompozisyon için Nesneler oluşturmak için nesne modelinden yararlanmak kolaydır.

Nesne olarak adlandırdığımız bu ilişki için, geliştirmek her zaman nesnenin iç durumunun ve sınıf davranışlarının ayrıldığını göz önünde bulundurur. Ve herhangi bir veri referansının herhangi bir paket / sınıf davranışını kullanmasına izin verebilir / izin verebilirsiniz. Paket nesnenin "duygusal" durumunu anlayabildiğinden.


Perl'in paketlerin ad alanlarıyla nasıl çalıştığı ve ad alanınızda kayıtlı durumlarla nasıl çalıştığı ile aynı duyurular. Çünkü bu namespace :: clean gibi pragmalar var. Ancak işleri daha basit tutmaya çalışın.
Steven Koch

-9

Örneğin, herhangi bir Bug nesnesinin kutsanmış bir karma olacağından eminseniz, (::!) Eksik kodu Bug :: print_me yönteminde doldurabilirsiniz:

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

Şimdi, print_me yöntemi Bug sınıfına kutsanmış herhangi bir karmaya başvuru yoluyla çağrıldığında, $ self değişkeni ilk bağımsız değişken olarak iletilen başvuruyu ayıklar ve sonra print deyimleri kutsanmış karmanın çeşitli girişlerine erişir.


@darch Bu cevap hangi kaynaktan intihal edildi?
Anderson Green
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.