web-dev-qa-db-ja.com

Perlの「祝福」は正確に何をしますか?

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

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

しかし、そのハッシュ参照に対して「祝福」していることは何でしょうか?

136
user47145

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

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

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

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

わかりやすくするために、例を次のように記述します。

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

編集: kixx 's good answer を参照してください。

138
Gordon Wilson

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

参照が何であるかは問題ではなく、ハッシュ(最も一般的なケース)、配列(それほど一般的ではない)、スカラー(通常、これは inside-outオブジェクト =)、正規表現、サブルーチン、またはTYPEGLOB(本を参照してください オブジェクト指向Perl:Damian Conwayによる概念およびプログラミング手法の包括的なガイド 有用な例)=またはファイルまたはディレクトリへの参照ハンドル(最も一般的でないケース)。

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

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

77
kixx

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

9
chaos

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

bless REF, CLASSNAME
bless REF

戻り値

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

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

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

ここにあるものは私にはあまりクリックしなかったので、ここで答えを提供します。

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つの方法は、個々の動物にサウンドの実行方法を教えることです。これは、各猫が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個のperformSoundsを意味します。独自のサウンドを検索して出力するすべての動物で使用される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」はありません。オブジェクトをパッケージに祝福すると、そのオブジェクトにパッケージへの参照が与えられ、パッケージ内の関数を呼び出すと、引数は1スロット分オフセットされ、最初の引数($ _ [0]またはshift)は次と同等になります。 javascriptの「this」。次に、JavaScriptのプロトタイプモデルをある程度シミュレートできます。

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

Mooseなど、この制限を表現力で橋渡しする独自の方法を作成するPerlライブラリがいくつかあります。

なぜ混乱するのか

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

やりたいこと

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

これは素朴な試みです:問題は、「呼び出し」が「何を呼び出したのか」を知らないため、オブジェクトにメソッドがあるかどうかをチェックする汎用のPerl関数「objectInvokeMethod(object、method)」である可能性があることです、またはそのプロトタイプがそれを持っているか、そのプロトタイプがそれを最後まで到達してそれを見つけるまで(プロトタイプ継承)持っています。 Perlにはそれを行うための素敵な評価マジックがありますが、私はそれを後で試すことができる何かのために残しておきます。

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

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

とにかく、この投稿が誰かに役立つといいのですが。

4
Dmitry

Iこの考え方に従って、開発オブジェクト指向Perlをガイドします。

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

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

1
Steven Koch