我会在这里提供一个答案,因为这里的答案并没有完全符合我的要求。
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'));
})->();
无论如何,希望有人会发现这篇文章很有用。