Perl的“祝福”到底是做什么的?

时间:2008-12-24 19:48:52

标签: perl bless

我理解在类的“新”方法中使用Perl中的“bless”关键字:

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

但究竟是什么“祝福”对哈希引用做了什么呢?

8 个答案:

答案 0 :(得分:136)

通常,bless将对象与类关联。

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

现在,当您在$object上调用方法时,Perl知道要搜索该方法的包。

如果省略第二个参数,如在您的示例中,则使用当前包/类。

为了清楚起见,您的示例可能写成如下:

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

编辑:有关详细信息,请参阅kixx的好answer

答案 1 :(得分:77)

bless将引用与包相关联。

引用的内容并不重要,它可以是散列(最常见的情况),数组(不常见),标量(通常这表示inside-out object),到正则表达式,子例程或TYPEGLOB(参见书籍Object Oriented Perl: A Comprehensive Guide to Concepts and Programming Techniques by Damian Conway以获取有用的示例)或甚至是对文件或目录句柄的引用(最不常见的情况)。

效果bless - 具有允许您将特殊语法应用于受祝福的引用。

例如,如果受祝福的引用存储在$obj中(由bless与包“Class”相关联),则$obj->foo(@args)将调用子例程foo并通过作为第一个参数,引用$obj后跟其余参数(@args)。子程序应在包“Class”中定义。如果包“Class”中没有子例程foo,则将搜索其他包的列表(从包“Class”中的数组@ISA获取)并且第一个子例程foo发现将被召唤。

答案 2 :(得分:9)

简短版本:它将散列标记为附加到当前包命名空间(以便该包提供其类实现)。

答案 3 :(得分:5)

此函数告诉REF引用的实体它现在是CLASSNAME包中的对象,或者如果省略CLASSNAME则是当前包。建议使用两种形式的祝福。

示例

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

答案 4 :(得分: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来打印动物自己的声音。

这样做的一种方法是教每个动物如何做声音。这意味着每个Cat都有自己的重复功能来执行声音。

# 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.

})();

我们希望每个动物都有一个功能,可以查找该动物自己的声音,而不是在构造时对其进行硬编码。

祝福让我们使用包作为对象的原型。这样,对象就知道它被“引用”的“包”,并且反过来可以让包中的函数“到达”从“包对象”的构造函数创建的特定实例: / p>

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的“这个”。反过来,您可以在某种程度上模拟JavaScript的原型模型。

不幸的是,由于你需要每个“类”拥有自己的包,因此在运行时创建“新类”是不可能的(据我的理解),而在javascript中,你根本不需要包,因为“ new“关键字组成一个匿名的hashmap,供您在运行时用作包,您可以在其中添加新功能并动态删除功能。

有一些Perl库创建了自己的方法来弥补表达性的这种限制,例如Moose。

为什么会出现混乱?

因为包裹。我们的直觉告诉我们将对象绑定到包含其原型的hashmap。这让我们可以像运行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'));
})->();

无论如何,希望有人会发现这篇文章很有用。

答案 5 :(得分:4)

伴随着许多好的答案,特别区别bless版的引用是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
  FLAGS = (ROK)
  RV = 0x12a5a68
  SV = PVHV(0x12ab980) at 0x12a5a68
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ...
      SV = IV(0x12a5ce0) at 0x12a5cf0
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1
---
SV = IV(0x12cb8b8) at 0x12cb8c8
  REFCNT = 1
  FLAGS = (PADMY,ROK)
  RV = 0x12c26b0
  SV = PVHV(0x12aba00) at 0x12c26b0
    REFCNT = 1
    FLAGS = (OBJECT,SHAREKEYS)
    STASH = 0x12d5300   "Class"
    ...
      SV = IV(0x12c26b8) at 0x12c26c8
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 10

已知1)它是一个对象2)它属于哪个包,这通知了它的使用。

例如,在遇到对该变量的取消引用($obj->name)时,将在包(或层次结构)中搜索具有该名称的子对象,将该对象作为第一个参数传递,等等。

答案 6 :(得分:1)

我遵循这个想法来指导面向开发对象的Perl。

Bless将任何数据结构引用与类相关联。考虑到Perl如何创建继承结构(在一种树中),很容易利用对象模型来创建用于合成的对象。

对于这种关联,我们称之为对象,开发时始终要记住对象的内部状态和类行为是分开的。您可以祝福/允许任何数据引用使用任何包/类行为。 因为包装可以理解&#34;情感&#34;对象的状态。

答案 7 :(得分:-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类中的散列的引用调用print_me方法时,$ self变量将提取作为第一个参数传递的引用,然后print语句访问其中的各个条目。祝福哈希。