Chính xác thì Perl '' s phước lành thế giới làm gì?


141

Tôi hiểu rằng người ta sử dụng từ khóa "phước lành" trong Perl bên trong phương thức "mới" của một lớp:

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

Nhưng chính xác thì "phước lành" đang làm gì với tham chiếu băm đó?


2
Xem "Bless My Reference" trở lại từ năm 1999. Trông khá chi tiết. ( Thật không may, mục nhập thủ công Perl không có nhiều điều để nói về điều đó, thật không may.)
Jon Skeet

Câu trả lời:


142

Nói chung, blessliên kết một đối tượng với một lớp.

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

Bây giờ khi bạn gọi một phương thức trên $object, Perl biết gói nào để tìm kiếm phương thức đó.

Nếu đối số thứ hai bị bỏ qua, như trong ví dụ của bạn, gói / lớp hiện tại được sử dụng.

Để rõ ràng, ví dụ của bạn có thể được viết như sau:

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

EDIT: Xem câu trả lời hay của kixx để biết thêm chi tiết.


78

bless liên kết một tài liệu tham khảo với một gói.

Không quan trọng tham chiếu là gì, nó có thể là hàm băm (trường hợp phổ biến nhất), đối với một mảng (không phổ biến), đối với vô hướng (thường điều này biểu thị một đối tượng từ trong ra ngoài ), với biểu thức chính quy , chương trình con hoặc TYPEGLOB (xem cuốn sách Hướng đối tượng Perl: Hướng dẫn toàn diện về khái niệm và kỹ thuật lập trình của Damian Conway để biết ví dụ hữu ích) hoặc thậm chí tham chiếu đến xử lý tệp hoặc thư mục (trường hợp ít gặp nhất).

Hiệu ứng blesscó là nó cho phép bạn áp dụng cú pháp đặc biệt cho tham chiếu may mắn.

Ví dụ: nếu một tham chiếu may mắn được lưu trữ trong $obj(được liên kết bởi blessgói "Class"), thì $obj->foo(@args)sẽ gọi một chương trình con foovà truyền làm đối số đầu tiên, tham chiếu $objtheo sau là phần còn lại của các đối số ( @args). Chương trình con nên được định nghĩa trong gói "Class". Nếu không có chương trình con footrong gói "Class", một danh sách các gói khác (được tạo thành mảng @ISAtrong gói "Class") sẽ được tìm kiếm và chương trình con đầu tiên foođược tìm thấy sẽ được gọi.


16
Tuyên bố ban đầu của bạn là không chính xác. Vâng, phước lành lấy một tham chiếu làm đối số đầu tiên của nó, nhưng đó là biến tham chiếu được ban phước, không phải là tham chiếu chính nó. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \% h; ban phước cho $ h, "Somepackage"; $ j = \% h; in $ j-> UNIVERSAL :: can ("foo") -> () '42
convert42

1
Giải thích của Kixx là toàn diện. Chúng ta không nên bận tâm với việc chọn bộ chuyển đổi trên các chi tiết lý thuyết.
Phúc cho Geek

19
@Blessed Geek, Đó không phải là chi tiết lý thuyết. Sự khác biệt có ứng dụng thực tế.
ikegami

3
Liên kết perlfoundation.org cũ cho "đối tượng từ trong ra ngoài", tốt nhất là, đằng sau một bức tường đăng nhập bây giờ. Liên kết Archive.org của bản gốc là ở đây .
ruffin

2
Có lẽ điều này sẽ phục vụ thay cho liên kết bị hỏng @harmic đã nhận xét về: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

Phiên bản ngắn: nó đánh dấu hàm băm đó như được gắn vào không gian tên gói hiện tại (để gói đó cung cấp triển khai lớp của nó).


7

Hàm này cho thực thể được tham chiếu bởi REF rằng bây giờ nó là một đối tượng trong gói CLASSNAME hoặc gói hiện tại nếu bỏ qua ClassNAME. Nên sử dụng hình thức hai đối số của phước lành.

Ví dụ :

bless REF, CLASSNAME
bless REF

Giá trị trả lại

Hàm này trả về tham chiếu đến một đối tượng được ban phước trong ClassNAME.

Ví dụ :

Sau đây là mã ví dụ hiển thị cách sử dụng cơ bản của nó, tham chiếu đối tượng được tạo bằng cách ban phước cho một tham chiếu đến lớp của gói -

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

Tôi sẽ cung cấp câu trả lời ở đây vì những câu hỏi ở đây không phù hợp với tôi.

Chức năng ban phước của Perl liên kết mọi tham chiếu đến tất cả các chức năng trong một gói.

Tại sao chúng ta cần điều này?

Hãy bắt đầu bằng cách thể hiện một ví dụ trong 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
})();

Bây giờ, hãy loại bỏ cấu trúc lớp và thực hiện mà không cần nó:

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

Hàm lấy một bảng băm của các thuộc tính không có thứ tự (vì sẽ không có nghĩa là phải viết các thuộc tính theo một thứ tự cụ thể trong các ngôn ngữ động trong năm 2016) và trả về một bảng băm với các thuộc tính đó hoặc nếu bạn quên đặt từ khóa mới, nó sẽ không đặt từ khóa mới sẽ trả về toàn bộ bối cảnh toàn cầu (ví dụ: cửa sổ trong trình duyệt hoặc toàn cầu trong nodejs).

Perl không có "cái này" hay "mới" hay "lớp", nhưng nó vẫn có thể có một chức năng hoạt động tương tự. Chúng tôi sẽ không có nhà xây dựng cũng như nguyên mẫu, nhưng chúng tôi sẽ có thể tạo ra các động vật mới theo ý muốn và sửa đổi các thuộc tính riêng lẻ của chúng.

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

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

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

Bây giờ, chúng ta có một vấn đề: Điều gì sẽ xảy ra nếu chúng ta muốn con vật tự thực hiện âm thanh thay vì chúng ta in giọng nói của chúng là gì. Đó là, chúng tôi muốn một hàm PerformanceSound in âm thanh của chính con vật.

Một cách để làm điều này là bằng cách dạy cho từng động vật cách phát ra âm thanh. Điều này có nghĩa là mỗi Cat có chức năng trùng lặp riêng để thực hiện.

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

Điều này là xấu vì PerformanceSound được đặt dưới dạng một đối tượng chức năng hoàn toàn mới mỗi khi động vật được xây dựng. 10000 động vật có nghĩa là 10000 biểu diễn. Chúng tôi muốn có một hàm PerformanceSound duy nhất được sử dụng bởi tất cả các động vật tìm kiếm âm thanh của riêng chúng và in nó.

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

Đây là nơi song song với Perl kinda dừng lại.

Toán tử mới của JavaScript không phải là tùy chọn, nếu không có nó, "cái này" bên trong các phương thức đối tượng sẽ làm hỏng phạm vi toàn cầu:

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

})();

Chúng tôi muốn có một chức năng cho mỗi Động vật tìm kiếm âm thanh của chính động vật đó thay vì mã hóa nó khi xây dựng.

Blessing cho phép chúng ta sử dụng một gói làm nguyên mẫu của các đối tượng. Theo cách này, đối tượng nhận thức được "gói" mà nó được "tham chiếu đến" và đến lượt nó có thể có các hàm trong gói "tiếp cận" các thể hiện cụ thể được tạo từ hàm tạo của "đối tượng gói" đó:

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

Tóm tắt / TL; DR :

Perl không có "cái này", "đẳng cấp", cũng không "mới". ban phước cho một đối tượng cho một gói cung cấp cho đối tượng đó một tham chiếu đến gói đó và khi nó gọi các hàm trong gói đó, các đối số của chúng sẽ được bù bởi 1 vị trí và đối số đầu tiên ($ _ [0] hoặc shift) sẽ tương đương với "cái này" của javascript. Đổi lại, bạn có thể mô phỏng phần nào mô hình nguyên mẫu của JavaScript.

Thật không may, theo cách hiểu của tôi, không thể tạo "lớp mới" khi bạn cần mỗi "lớp" để có gói riêng, trong khi trong javascript, bạn không cần gói nào cả, như từ khóa "mới" tạo nên một hashmap ẩn danh để bạn sử dụng như một gói trong thời gian chạy mà bạn có thể thêm các chức năng mới và loại bỏ các chức năng một cách nhanh chóng.

Có một số thư viện Perl tạo ra những cách riêng để bắc cầu giới hạn này trong tính biểu cảm, chẳng hạn như Moose.

Tại sao lại nhầm lẫn? :

Vì gói. Trực giác của chúng ta bảo chúng ta liên kết đối tượng với một hashmap chứa nguyên mẫu của nó. Điều này cho phép chúng tôi tạo "gói" trong thời gian chạy như JavaScript có thể. Perl không có tính linh hoạt như vậy (ít nhất là không được tích hợp sẵn, bạn phải phát minh ra nó hoặc lấy nó từ các mô-đun khác), và đến lượt biểu cảm thời gian chạy của bạn bị cản trở. Gọi nó là "phước lành" cũng không làm điều đó nhiều ủng hộ.

Chúng tôi muốn làm gì :

Một cái gì đó như thế này, nhưng có ràng buộc với đệ quy bản đồ nguyên mẫu, và hoàn toàn bị ràng buộc với nguyên mẫu hơn là phải thực hiện nó một cách rõ ràng.

Đây là một nỗ lực ngây thơ của nó: vấn đề là "cuộc gọi" không biết "cái gì đã gọi nó", vì vậy nó cũng có thể là một hàm perl phổ quát "objectInvokeMethod (object, method)" để kiểm tra xem đối tượng có phương thức không hoặc nguyên mẫu của nó có nó, hoặc nguyên mẫu của nó có nó, cho đến khi nó đi đến tận cùng và tìm thấy nó hoặc không (kế thừa nguyên mẫu). Perl có phép thuật tốt đẹp để làm điều đó nhưng tôi sẽ để nó cho một cái gì đó tôi có thể thử làm sau.

Dù sao đây là ý tưởng:

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

Dù sao, hy vọng ai đó sẽ tìm thấy bài viết này hữu ích.


Không thể tạo các lớp mới trong thời gian chạy. my $o = bless {}, $anything;sẽ ban phước cho một đối tượng vào $anythinglớp. Tương tự, {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};sẽ tạo một phương thức có tên 'somesub' trong lớp có tên trong $anything. Đây là tất cả có thể tại thời gian chạy. "Có thể", tuy nhiên, không làm cho nó trở thành một cách thực hành tuyệt vời để sử dụng mã hàng ngày. Nhưng nó rất hữu ích trong việc xây dựng các hệ thống lớp phủ đối tượng như Moose hoặc Moo.
DavidO

thật thú vị, vì vậy bạn đang nói rằng tôi có thể ban phước cho một người giới thiệu vào một lớp có tên được quyết định trong thời gian chạy. Có vẻ thú vị, và làm mất hiệu lực unfortunately it makes it impossible(to my understanding) to create "new classes" at runtimeyêu cầu của tôi . Tôi đoán rằng mối quan tâm của tôi cuối cùng đã làm cho nó trở nên ít trực quan hơn đáng kể để thao túng / hướng nội hệ thống gói trong thời gian chạy, nhưng cho đến nay tôi đã thất bại trong việc hiển thị bất cứ điều gì vốn dĩ nó không thể làm được. Hệ thống gói dường như hỗ trợ tất cả các công cụ cần thiết để thêm / xóa / kiểm tra / sửa đổi chính nó khi chạy.
Dmitry

Chính xác; bạn có thể thao tác bảng biểu tượng của Perl theo lập trình và do đó có thể thao tác các gói của Perl và các thành viên của gói trong thời gian chạy, ngay cả khi không khai báo "gói Foo" ở bất cứ đâu. Kiểm tra và thao tác bảng biểu tượng trong thời gian chạy, ngữ nghĩa AUTOLOAD, thuộc tính chương trình con, liên kết các biến với các lớp ... có nhiều cách để có được dưới mui xe. Một số trong số chúng hữu ích cho việc tự động tạo API, công cụ xác thực, API tự động mã hóa; chúng ta không thể dự đoán tất cả các trường hợp sử dụng. Tự bắn vào chân mình cũng là kết quả có thể xảy ra của mánh khóe đó.
DavidO

4

Cùng với một số câu trả lời hay, điều đặc biệt phân biệt một blesstham chiếu -ed là SV nó dành cho nó một bổ sung FLAGS( OBJECT) và mộtSTASH

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'

Các bản in, với cùng một phần (và không liên quan cho phần này) bị loại bỏ

SV = IV (0x12d5530) tại 0x12d5540
  REFCNT = 1
  CỜ = (ROK)
  RV = 0x12a5a68
  SV = PVHV (0x12ab980) tại 0x12a5a68
    REFCNT = 1
    CỜ = (CHIA SẺ)
    ...
      SV = IV (0x12a5ce0) tại 0x12a5cf0
      REFCNT = 1
      CỜ = (IOK, pIOK)
      IV = 1
---
SV = IV (0x12cb8b8) tại 0x12cb8c8
  REFCNT = 1
  CỜ = (PADMY, ROK)
  RV = 0x12c26b0
  SV = PVHV (0x12aba00) tại 0x12c26b0
    REFCNT = 1
    FLAGS = (ĐỐI TƯỢNG, CHIA SẺ)
    STASH = 0x12d5300 "Lớp"
    ...
      SV = IV (0x12c26b8) tại 0x12c26c8
      REFCNT = 1
      CỜ = (IOK, pIOK)
      IV = 10

Với điều đó, người ta biết rằng 1) nó là một đối tượng 2) nó thuộc về gói nào và điều này cho biết việc sử dụng nó.

Ví dụ: khi gặp hội thảo về biến đó ( $obj->name), một phụ có tên đó được tìm kiếm trong gói (hoặc phân cấp), đối tượng được truyền làm đối số đầu tiên, v.v.


1

Tôi làm theo suy nghĩ này để hướng dẫn Perl hướng đối tượng phát triển.

Bless liên kết bất kỳ tham chiếu cấu trúc dữ liệu với một lớp. Dựa vào cách Perl tạo cấu trúc thừa kế (trong một loại cây), có thể dễ dàng tận dụng mô hình đối tượng để tạo Đối tượng cho thành phần.

Đối với sự liên kết này, chúng tôi gọi là đối tượng, để phát triển luôn có ý nghĩ rằng trạng thái bên trong của đối tượng và hành vi lớp được tách ra. Và bạn có thể ban phước / cho phép mọi tham chiếu dữ liệu sử dụng bất kỳ hành vi gói / lớp nào. Vì gói có thể hiểu trạng thái "cảm xúc" của đối tượng.


Dưới đây là cùng một thông báo với cách Perl hoạt động với không gian tên của các gói và cách làm việc với các trạng thái được đăng ký trong không gian tên của bạn. Bởi vì điều này tồn tại các pragma như sử dụng không gian tên :: sạch. Nhưng cố gắng giữ mọi thứ đơn giản hơn có thể.
Steven Koch

-9

Ví dụ: nếu bạn có thể tin tưởng rằng bất kỳ đối tượng Bug nào sẽ là một hàm băm may mắn, cuối cùng bạn có thể điền mã bị thiếu trong phương thức 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";
 }

Bây giờ, bất cứ khi nào phương thức print_me được gọi thông qua tham chiếu đến bất kỳ hàm băm nào được đưa vào lớp Bug, biến $ self sẽ trích xuất tham chiếu được truyền dưới dạng đối số đầu tiên và sau đó các câu lệnh in truy cập vào các mục khác nhau của hàm băm.


@darch Từ nguồn nào câu trả lời này đạo văn?
Anderson Green
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.