Я дам здесь ответ, так как те, что здесь, не совсем мне понравились.
Функция благословения Perl связывает любую ссылку на все функции внутри пакета.
Зачем нам это нужно?
Давайте начнем с выражения примера на 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};
})->();
Теперь у нас есть проблема: что, если мы хотим, чтобы животное исполняло звуки самостоятельно, а не мы печатали, каков их голос. То есть нам нужна функция executeSound, которая печатает собственный звук животного.
Один из способов сделать это - научить каждого отдельного животного тому, как это звучит. Это означает, что у каждого кота есть своя функция дублирования для выполнения звука.
# 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'}();
})->();
Это плохо, потому что executeSound ставится как совершенно новый функциональный объект каждый раз, когда создается животное. 10000 животных означает 10000 звуков. Мы хотим иметь единственную функцию executeSound, которая используется всеми животными, которая ищет свой собственный звук и печатает его.
(() => {
'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.
})();
Мы хотим иметь одну функцию для каждого животного, которая ищет собственный звук этого животного, а не программирует его при строительстве.
Благословение позволяет нам использовать пакет в качестве прототипа объектов. Таким образом, объект знает о «пакете», на который он «ссылается», и, в свою очередь, может иметь функции в пакете, «достигающие» конкретных экземпляров, которые были созданы из конструктора этого «объекта пакета»:
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.
Почему путаница? :
Из-за пакетов. Наша интуиция говорит нам привязать объект к хэш-карте, содержащей его прототип. Это позволяет нам создавать «пакеты» во время выполнения, как может 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'));
})->();
В любом случае, надеюсь, кто-нибудь найдет этот пост полезным.