Perlの「祝福」は正確には何をするのでしょうか?


142

私は、クラスの「新しい」メソッド内でPerlの「bless」キーワードを使用していることを理解しています。

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

しかし、そのハッシュ参照に対して正確に何を「祝福」しているのでしょうか。


2
1999年以降の「ブレントマイリファレント」を参照してください。かなり詳細に見えます。(残念ながら、Perlのマニュアルエントリにはそれほど多くのことを言うことはできません。)
Jon Skeet

回答:


143

一般にbless、オブジェクトをクラスに関連付けます。

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

でメソッドを呼び出すと$object、Perlはメソッドを検索するパッケージを認識します。

例のように2番目の引数が省略されている場合、現在のパッケージ/クラスが使用されます。

わかりやすくするために、例は次のように書くことができます。

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

編集:もう少し詳しくは、kixxの適切な回答を参照してください。


79

bless 参照をパッケージに関連付けます。

参照が何であるかは関係ありません。ハッシュ(最も一般的なケース)、配列(それほど一般的ではない)、スカラー(通常、これはオブジェクトがインサイドアウトであることを示します)、正規表現などです。 、サブルーチン、またはTYPEGLOB(有用な例については、オブジェクト指向Perl:概念とプログラミング手法の総合ガイド(Damian Conway著)を参照)、またはファイルまたはディレクトリハンドルへの参照(最も一般的ではないケース)です。

bless-ing の効果は、祝福された参照に特別な構文を適用できることです。

たとえば、祝福された参照が$objblessパッケージ "Class"に関連付けられて)に格納されている場合、$obj->foo(@args)サブルーチンを呼び出し、foo最初の引数として参照を渡し$obj、その後に残りの引数を渡します(@args)。サブルーチンはパッケージ「クラス」で定義する必要があります。fooパッケージ「クラス」にサブルーチンがない場合、他のパッケージのリスト(@ISAパッケージ「クラス」の配列から取得)が検索され、最初にfoo見つかったサブルーチンが呼び出されます。


16
あなたの最初のステートメントは正しくありません。はい、blessは最初の引数として参照を受け取りますが、blessされるのは参照自体ではなく参照変数です。$ perl -le 'sub Somepackage :: foo {42}; %h =(); $ h = \%h; $ hを祝福してください、「Somepackage」; $ j = \%h; $ j-> UNIVERSAL :: can( "foo")->()
converter42

1
Kixxの説明は包括的です。コンバーターが理論的な特徴点を選択することに煩わされるべきではありません。
Blessed Geek

19
@祝福されたオタク、それは理論的な特徴ではありません。違いは実用的です。
池上2011年

3
「インサイドアウトオブジェクト」の古いperlfoundation.orgリンクは、せいぜい、ログインウォールの背後に隠れています。オリジナルのArchive.orgリンクはこちらです。
ルフィン、2015年

2
:おそらくこれは、上のコメント壊れたリンク@harmicの代わりに機能しますperldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

ショートバージョン:現在のパッケージの名前空間にアタッチされているものとしてそのハッシュをマークしています(そのパッケージがクラスの実装を提供するように)。


7

この関数は、REFによって参照されるエンティティに、それがCLASSNAMEパッケージ内のオブジェクト、またはCLASSNAMEが省略されている場合は現在のパッケージであることを通知します。2引数形式のblessの使用をお勧めします。

bless REF, CLASSNAME
bless REF

戻り値

この関数は、CLASSNAMEにblessされたオブジェクトへの参照を返します。

以下は、その基本的な使用法を示すコード例です。オブジェクト参照は、パッケージのクラスへの参照をblessすることによって作成されます-

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

ここでは答えがわからなかったので、ここで答えを示します。

Perlのbless関数は、参照をパッケージ内のすべての関数に関連付けます。

なぜこれが必要なのでしょうか?

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

ここで、クラス構造を取り除き、それなしで実行できるようにします。

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

この関数は、順序付けされていないプロパティのハッシュテーブルを取得し(2016年に動的言語でプロパティを特定の順序で記述する必要がないので意味がないため)、それらのプロパティを含むハッシュテーブルを返します。または、新しいキーワードを入力し忘れた場合は、グローバルコンテキスト全体を返します(例:ブラウザのウィンドウまたはnodejsのグローバル)。

Perlには「this」も「new」も「class」もありませんが、同じように動作する関数を持つことができます。コンストラクタもプロトタイプもありませんが、自由に新しい動物を作成し、個々のプロパティを変更することができます。

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

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

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

さて、問題があります。動物が自分の声を印刷するのではなく、自分で音を鳴らしたい場合はどうでしょう。つまり、動物自身の音を出力する関数performSoundが必要です。

これを行う1つの方法は、個々の動物に音の作り方を教えることです。つまり、各Catには、performSoundを実行するための独自の複製機能があります。

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

動物が構築されるたびに、performSoundが完全に新しい関数オブジェクトとして配置されるため、これは悪いことです。10000動物は、10000のパフォーマンスサウンドを意味します。自分の音を検索して出力するすべての動物が使用する単一の関数performSoundを作成します。

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

ここで、Perlとの並列処理がやや停止します。

JavaScriptの新しい演算子はオプションではなく、それがないと、オブジェクトメソッド内の「this」がグローバルスコープを破壊します。

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

})();

作成時にハードコーディングするのではなく、その動物自身の音を検索する各動物に1つの関数を設定します。

祝福により、パッケージをオブジェクトのプロトタイプとして使用できます。このようにして、オブジェクトは「参照される」「パッケージ」を認識し、その「パッケージオブジェクト」のコンストラクターから作成された特定のインスタンスに「到達する」パッケージ内の関数を持つことができます。

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

要約/ TL; DR

Perlには「this」、「class」、「new」はありません。オブジェクトをパッケージにblessすると、そのオブジェクトにパッケージへの参照が与えられ、パッケージ内の関数を呼び出すと、引数は1スロットだけオフセットされ、最初の引数($ _ [0]またはshift)は次のようになります。 javascriptの「this」。次に、JavaScriptのプロトタイプモデルを多少シミュレートできます。

残念ながら、実行時に "新しいクラス"を作成することは(私の理解では)不可能です。これは、各 "クラス"が独自のパッケージを持つ必要があるためです。一方、JavaScriptでは、 "新しい"キーワードとしてパッケージをまったく必要としません。実行時にパッケージとして使用するための匿名ハッシュマップを作成します。これには、新しい関数を追加したり、その場で関数を削除したりできます。

Mooseのように、この制限を表現力で埋める独自の方法を作成するPerlライブラリがいくつかあります。

なぜ混乱するのですか?

パッケージのため。私たちの直感は、オブジェクトをそのプロトタイプを含むハッシュマップにバインドするように指示しています。これにより、JavaScriptのように実行時に「パッケージ」を作成できます。Perlにはそのような柔軟性がなく(少なくとも組み込みではなく、それを発明するか、他のモジュールから取得する必要があります)、実行時の表現力が妨げられます。それを「祝福」と呼んでも、どちらも有利にはなりません。

私たちがしたいこと

このようなものですが、プロトタイプマップに再帰的にバインドしており、明示的に行う必要はなく、暗黙的にプロトタイプにバインドされています。

これは素朴な試みです:問題は、「呼び出し」が「それを何と呼んだか」を知らないことです。そのため、オブジェクトがメソッドを持っているかどうかをチェックするユニバーサルperl関数「objectInvokeMethod(object、method)」かもしれません、またはそのプロトタイプがそれを持っているか、またはそのプロトタイプがそれを持っている、それが最後に到達してそれを見つけるまで(プロトタイプ継承)。Perlにはそれを行うための素晴らしいevalマジックがありますが、後でやることを試すことができる何かのために残しておきます。

とにかくここにアイデアがあります:

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

とにかく、うまくいけば、誰かがこの投稿が役に立つと思うでしょう。


実行時に新しいクラスを作成することは不可能ではありません。 my $o = bless {}, $anything;オブジェクトを$anythingクラスに祝福します。同様に、で指定{no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};されたクラスに「somesub」という名前のメソッドを作成し$anythingます。これはすべて実行時に可能です。ただし、「可能性」があるからといって、毎日のコードを操作するのは良い習慣ではありません。ただし、MooseやMooなどのオブジェクトオーバーレイシステムの構築には役立ちます。
ダビド

興味深いので、実行時に名前が決定されるクラスに指示対象を祝福できると言っています。興味深いようで、私のunfortunately it makes it impossible(to my understanding) to create "new classes" at runtime主張を無効にします。結局のところ、私の懸念は、実行時にパッケージシステムを操作/イントロスペクトすることの直感性が大幅に低下することになると思いますが、これまでのところ、本質的にそれができないことを示すことに失敗しました。パッケージシステムは、実行時に自身を追加/削除/検査/変更するために必要なすべてのツールをサポートしているようです。
ドミトリー

これは正しいです; プログラムでPerlのシンボルテーブルを操作できるため、「package Foo」をどこにも宣言していなくても、実行時にPerlのパッケージとパッケージのメンバーを操作できます。実行時のシンボルテーブルの検査と操作、AUTOLOADセマンティクス、サブルーチンの属性、クラスへの変数の関連付け...内部にはさまざまな方法があります。それらのいくつかは、APIの自動生成、検証ツール、APIの自動ドキュメント化に役立ちます。すべてのユースケースを予測することはできません。足で自分を撃つこともまた、そのような策略の結果の可能性があります。
DavidO

4

多くの良い答えとともに、bless-ed参照を明確に区別するのはSV 、追加のFLAGSOBJECT)とSTASH

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'

同じ(そしてこれとは無関係な)パーツが抑制された印刷

SV = IV(0x12d5530)at 0x12d5540
  REFCNT = 1
  フラグ=(韓国)
  RV = 0x12a5a68
  SV = PVHV(0x12ab980)at 0x12a5a68
    REFCNT = 1
    フラグ=(SHAREKEYS)
    ...
      SV = IV(0x12a5ce0)at 0x12a5cf0
      REFCNT = 1
      フラグ=(IOK、pIOK)
      IV = 1
---
SV = IV(0x12cb8b8)at 0x12cb8c8
  REFCNT = 1
  フラグ=(PADMY、ROK)
  RV = 0x12c26b0
  SV = PVHV(0x12aba00)at 0x12c26b0
    REFCNT = 1
    フラグ=(OBJECT、SHAREKEYS)
    STASH = 0x12d5300 "クラス"
    ...
      SV = IV(0x12c26b8)at 0x12c26c8
      REFCNT = 1
      フラグ=(IOK、pIOK)
      IV = 10

これで、1)オブジェクトである、2)どのパッケージに属しているのかがわかります。これにより、その使用法がわかります。

たとえば、その変数の逆参照が発生した場合($obj->name)、その名前のサブルーチンがパッケージ(または階層)で検索され、オブジェクトが最初の引数として渡されます。


1

私はこの考えに従って、オブジェクト指向のPerl開発を導きました。

データ構造参照をクラスに関連付けます。Perlが(一種のツリーで)継承構造を作成する方法を考えると、オブジェクトモデルを利用して合成用のオブジェクトを作成するのは簡単です。

この関連付けでは、オブジェクトと呼ばれ、常に開発することは、オブジェクトの内部状態とクラスの動作が分離されていることを念頭に置いてください。そして、あらゆるデータ参照がパッケージ/クラスの振る舞いを使用することを祝福/許可することができます。パッケージはオブジェクトの「感情的な」状態を理解できるので。


これは、Perlがパッケージの名前空間を処理する方法と、名前空間に登録された状態を処理する方法に関する同じ発表です。これは、use namespace :: cleanのようなプラグマが存在するためです。しかし、物事をできるだけ簡単に保つようにしてください。
Steven Koch

-9

たとえば、Bugオブジェクトが祝福されたハッシュになると確信できる場合は、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";
 }

これで、Bugクラスにblessされたハッシュへの参照を介してprint_meメソッドが呼び出されると、$ self変数が最初の引数として渡された参照を抽出し、printステートメントがblessされたハッシュのさまざまなエントリにアクセスします。


@darchこの回答はどのソースから盗用されましたか?
アンダーソングリーン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.