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


141

我了解一个人在类的“ new”方法中使用了Perl中的“ bless”关键字:

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

但是,“祝福”到底对那个哈希引用做了什么?


2
请参阅1999年的“ Bless My Referents”。看起来非常详细。(不幸的是,Perl的手册上没有很多要说的。)
Jon Skeet

Answers:


142

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

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

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

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

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

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

编辑:更多细节请参见kixx的好答案


78

bless 将引用与包关联。

引用的内容无关紧要,可以是哈希(最常见的情况),数组(不太常见),标量(通常表示从里到外的对象),正则表达式,子例程或TYPEGLOB(有关有用的示例,请参见Damian Conway的《面向对象的Perl:概念和编程技术的综合指南》一书),甚至是对文件或目录句柄的引用(最不常见的情况)。

bless-ing 的作用是,它允许您将特殊语法应用于受祝福的引用。

例如,如果存储了一个受祝福的引用$objbless与包“ Class” 关联),$obj->foo(@args)则将调用一个子例程foo并将该引用作为$obj后面的其余参数(@args)作为第一个参数传递。该子例程应在包“类”中定义。如果foo软件包“ Class”中没有子例程,@ISA则将搜索其他软件包列表(从软件包“ Class”中的数组中获取)并调用foo找到的第一个子例程。


16
您的最初声明不正确。是的,bless将引用作为其第一个参数,但是受祝福的是引用变量,而不是引用本身。$ perl -le'sub Somepackage :: foo {42}; %h =(); $ h = \%h; 祝福$ h,“ Somepackage”;$ j = \%h; 打印$ j-> UNIVERSAL :: can(“ foo”)->()'42
converter42

1
Kixx的解释很全面。我们不应理会转换器在理论细节上的选择。
Blessed Geek

19
@Blessed Geek,这不是理论上的细节。两者之间有实际应用。
ikegami

3
现在,perlfoundation.org上用于“由内而外的对象”的旧链接最多只能在登录墙后面。原始文件的Archive.org链接在这里
鲁芬2015年

2
也许这可以代替@harmic评论的断开链接:perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

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


7

此函数告诉REF引用的实体,它现在是CLASSNAME包中的对象,如果省略CLASSNAME,则为当前包中的对象。建议使用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;
}

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年必须以特定顺序以动态语言编写属性是没有意义的),并返回具有这些属性的哈希表,或者如果您忘记放置new关键字,则该函数将返回整个全局上下文(例如,浏览器中的窗口或nodejs中的global)。

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的new运算符不是可选的,没有它,对象方法内部的“ 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.

})();

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

祝福让我们使用包作为对象的原型。这样,对象就知道了它被“引用”的“包”,进而可以使包中的函数“进入”从该“包对象”的构造函数创建的特定实例:

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”关键字组成一个匿名哈希图,供您在运行时用作程序包,您可以在其中动态添加或删除函数。

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

为什么会感到困惑?

因为包装。我们的直觉告诉我们将对象绑定到包含其原型的哈希图。这使我们可以像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'));
})->();

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


在运行时创建新类并非不可能。 my $o = bless {}, $anything;会祝福一个对象进入$anything全班。同样,{no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};将在中命名的类中创建一个名为“ somesub”的方法$anything。这在运行时都是可能的。但是,“可能”并不是每天使用代码的好习惯。但是在构建对象覆盖系统(例如Moose或Moo)时很有用。
DavidO

有趣,所以您说的是我可以将一个引用对象祝福给一个其名称在运行时确定的类。似乎很有趣,并且确实使我的unfortunately it makes it impossible(to my understanding) to create "new classes" at runtime主张无效。我想我的关注最终归结为它在运行时操纵/理解软件包系统的直观性明显下降,但是到目前为止,我未能显示其固有的功能。软件包系统似乎支持在运行时添加/删除/检查/修改自身所需的所有工具。
德米特里(Dmitry)

这是对的; 您可以以编程方式操作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)在0x12d5540
  REFCNT = 1
  标志=(ROK)
  RV = 0x12a5a68
  SV = PVHV(0x12ab980)在0x12a5a68
    REFCNT = 1
    标志=(SHAREKEYS)
    ...
      SV = IV(0x12a5ce0)在0x12a5cf0
      REFCNT = 1
      标志=(IOK,pIOK)
      IV = 1
---
SV = IV(0x12cb8b8)在0x12cb8c8
  REFCNT = 1
  标志=(PADMY,ROK)
  RV = 0x12c26b0
  SV = PVHV(0x12aba00)在0x12c26b0
    REFCNT = 1
    标志=(对象,密钥)
    STASH = 0x12d5300“类”
    ...
      SV = IV(0x12c26b8)在0x12c26c8
      REFCNT = 1
      标志=(IOK,pIOK)
      IV = 10

这样就知道1)它是一个对象2)它属于哪个包,并且这通知了它的使用。

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


1

我按照这种思想来指导开发面向对象的Perl。

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

对于这种关联,我们将其称为对象,要开发,始终要牢记对象的内部状态和类行为是分开的。您可以祝福/允许任何数据引用使用任何包/类行为。由于包可以理解对象的“情感”状态。


Perl如何与包的名称空间一起使用以及如何在名称空间中注册的状态一起使用,这是相同的声明。因为这存在使用use namespace :: clean之类的杂物。但是,尝试使事情变得更简单。
史蒂文·科赫

-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语句访问加倍的哈希值的各个条目。


@darch这个答案是从哪个来源抄袭的?
安德森·格林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.