Perl 的“祝福”到底是做啥的?

Posted

技术标签:

【中文标题】Perl 的“祝福”到底是做啥的?【英文标题】:What exactly does Perl's "bless" do?Perl 的“祝福”到底是做什么的? 【发布时间】:2010-09-28 08:58:16 【问题描述】:

我知道有人在 Perl 中的“新”方法中使用了“祝福”关键字:

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

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

【问题讨论】:

参见 1999 年的 "Bless My Referents"。看起来非常详细。 (不幸的是,Perl manual entry 对此没什么好说的。) 【参考方案1】:

一般来说,bless 将一个对象与一个类相关联。

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

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

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

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

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

编辑:请参阅 kixx 的 answer 了解更多详细信息。

【讨论】:

【参考方案2】:

bless 将引用与包相关联。

引用是什么并不重要,它可以是哈希(最常见的情况)、数组(不太常见)、标量(通常表示inside-out object)、正则表达式、子例程或 TYPEGLOB(有关有用的示例,请参阅本书 Object Oriented Perl: A Comprehensive Guide to Concepts and Programming Techniques by Damian Conway),甚至是对文件或目录句柄的引用(最不常见的情况)。

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

例如,如果一个祝福引用存储在$obj 中(由bless 与包“Class”相关联),那么$obj->foo(@args) 将调用一个子例程foo 并将引用$obj 作为第一个参数传递其次是其余的论点 (@args)。子程序应该在包“Class”中定义。如果包“Class”中没有子例程foo,则将搜索其他包的列表(取自包“Class”中的数组@ISA)并调用找到的第一个子例程foo

【讨论】:

您最初的陈述不正确。是的,bless 将引用作为其第一个参数,但祝福的是引用变量,而不是引用本身。 $ perl -le 'sub Somepackage::foo 42; %h=(); $h=\%h; bless $h, "Somepackage"; $j = \%h;打印 $j->UNIVERSAL::can("foo")->()' 42 Kixx 的解释很全面。我们不应该为转换器挑选理论细节而烦恼。 @Blessed Geek,这不是理论上的细节。差异有实际应用。 旧的 perlfoundation.org 链接“inside-out object”现在充其量是在登录墙后面。 Archive.org link of the original is here. 也许这将代替@harmic评论的断开链接:perldoc.perl.org/perlobj.html#Inside-Out-objects【参考方案3】:

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

【讨论】:

【参考方案4】:

这个函数告诉 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;

【讨论】:

【参考方案5】:

我会在这里提供一个答案,因为这里的答案并没有完全符合我的要求。

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

)();

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

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

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

有一些 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'));
)->();

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

【讨论】:

在运行时创建新类并非不可能。 my $o = bless , $anything; 将祝福一个对象进入$anything 类。同样,no strict 'refs'; *$anything . '::somesub' = sub my $self = shift; return $self->count++; 将在$anything 中命名的类中创建一个名为“somesub”的方法。这在运行时都是可能的。然而,“可能”并不是在日常代码中使用的好习惯。但它在构建对象覆盖系统(如 Moose 或 Moo)时很有用。 很有趣,所以你是说我可以祝福一个引用对象进入一个名称在运行时决定的类。看起来很有趣,并且确实使我的unfortunately it makes it impossible(to my understanding) to create "new classes" at runtime 声明无效。我想我的担忧最终归结为它在运行时操作/内省包系统的直观性显着降低,但到目前为止,我未能展示它本质上不能做的任何事情。包系统似乎支持在运行时添加/删除/检查/修改自身所需的所有工具。 这是正确的;您可以以编程方式操作 Perl 的符号表,因此可以在运行时操作 Perl 的包和包的成员,即使没有在任何地方声明“包 Foo”。运行时的符号表检查和操作、AUTOLOAD 语义、子例程属性、将变量绑定到类……有很多方法可以深入了解。其中一些对于自动生成 API、验证工具、自动记录 API 很有用;我们无法预测所有用例。射中自己的脚也是这种诡计的可能结果。【参考方案6】:

bless-ed 引用在内部的具体区别在于,引用的SV(存储在标量中)获取了一个额外的FLAGS 值(OBJECT),还有一个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

这样解释器就知道了

这是一个对象

它属于哪个包

这说明了它的用途。

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

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

这里同样宣布了 Perl 如何处理包的命名空间以及如何处理在你的命名空间中注册的状态。因为这存在像 use namespace::clean 这样的编译指示。但尽量让事情尽可能简单。【参考方案8】:

例如,如果您确信任何 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";
 

现在,每当 print_me 方法通过对已被祝福到 Bug 类中的任何哈希的引用调用时,$self 变量都会提取作为第一个参数传递的引用,然后打印语句访问祝福哈希。

【讨论】:

@darch 这个答案抄袭来自哪个来源?

以上是关于Perl 的“祝福”到底是做啥的?的主要内容,如果未能解决你的问题,请参考以下文章

“del”到底是做啥的?

selenium 中的 ime() 到底是做啥的?

DrawShadow 中的 Elevation 到底是做啥的?

.join() 方法到底是做啥的?

.join() 方法到底是做啥的?

removeOnCompletion = NO 到底是做啥的?