Что именно делает Perl "благослови"?
Я понимаю, что кто-то использует ключевое слово "bless" в Perl внутри метода "new" класса:
sub new {
my $self = bless { };
return $self;
}
Но что именно "благословит" делать с этой ссылкой на хэш?
9 ответов
В общем, bless
связывает объект с классом.
package MyClass;
my $object = { };
bless $object, "MyClass";
Теперь, когда вы вызываете метод на $object
Perl знает, какой пакет искать для метода.
Если второй аргумент опущен, как в вашем примере, используется текущий пакет / класс.
Для ясности ваш пример может быть записан следующим образом:
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
}
РЕДАКТИРОВАТЬ: См хороший ответ kixx для чуть более подробной информации.
bless
связывает ссылку с пакетом.
Неважно, к чему относится ссылка, это может быть хеш (наиболее распространенный случай), массив (не очень распространенный), скаляр (обычно это указывает на объект наизнанку), регулярное выражение, подпрограмма или TYPEGLOB (см. полезные примеры в книге " Объектно-ориентированный Perl: всеобъемлющее руководство по концепциям и методам программирования" Дамиана Конвея) или даже ссылку на дескриптор файла или каталога (наименее распространенный случай).
Эффект bless
Он имеет то, что позволяет применять специальный синтаксис к благословенной ссылке.
Например, если благословенная ссылка хранится в $obj
(связано с bless
с пакетом "Класс"), то $obj->foo(@args)
вызову подпрограмму foo
и передать в качестве первого аргумента ссылку $obj
следуют остальные аргументы (@args
). Подпрограмма должна быть определена в пакете "Класс". Если подпрограммы нет foo
в пакете "Класс", список других пакетов (взятых из массива @ISA
в пакете "Класс") будет найдена и первая подпрограмма foo
найден будет называться.
Эта функция сообщает объекту, на который ссылается 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;
}
Короткая версия: это пометка хэша как присоединенного к пространству имен текущего пакета (чтобы этот пакет предоставлял реализацию своего класса).
Я дам здесь ответ, так как те, что здесь, не совсем понравились мне.
Функция благословения 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 нет ни "этого", ни "класса", ни "нового". благословение объекта на пакет дает этому объекту ссылку на пакет, и когда он вызывает функции в пакете, их аргументы будут смещены на 1 слот, а первый аргумент ($_[0] или shift) будет эквивалентен javascript "это". В свою очередь, вы можете немного смоделировать модель прототипа JavaScript.
К сожалению, это делает невозможным (насколько я понимаю) создание "новых классов" во время выполнения, так как каждый "класс" должен иметь свой собственный пакет, тогда как в javascript пакеты вообще не нужны, поскольку ключевое слово "new" создает анонимную хэш-карту для использования в качестве пакета во время выполнения, к которому вы можете добавлять новые функции и удалять функции на лету.
Есть некоторые библиотеки Perl, создающие свои собственные способы преодоления этого ограничения в выразительности, такие как Moose.
Почему путаница?:
Из-за пакетов. Наша интуиция говорит нам привязать объект к хэш-карте, содержащей его прототип. Это позволяет нам создавать "пакеты" во время выполнения, как может JavaScript. Perl не обладает такой гибкостью (по крайней мере, не встроенной, вам нужно ее придумывать или извлекать из других модулей), и, в свою очередь, ваша выразительность во время выполнения сдерживается. Называя это "благослови", это не приносит пользы.
Что мы хотим сделать:
Примерно так, но есть привязка к прототипу рекурсивной карты и неявная привязка к прототипу вместо того, чтобы делать это явно.
Вот наивная попытка: проблема в том, что "call" не знает "как он был вызван", так что это может быть универсальная 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'));
})->();
В любом случае, надеюсь, кто-нибудь найдет этот пост полезным.
Наряду с рядом хороших ответов, что именно отличает 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) при 0x12d5540 REFCNT = 1 ФЛАГИ = (ROK) RV = 0x12a5a68 SV = PVHV(0x12ab980) при 0x12a5a68 REFCNT = 1 ФЛАГИ = (КЛЮЧИ)... 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
), подпрограмма с таким именем ищется в пакете (или иерархии), объект передается в качестве первого аргумента и т. д.
Следуя этой мысли, я руководствуюсь разработкой объектно-ориентированного Perl.
Благослови связь любой ссылки на структуру данных с классом. Учитывая, как Perl создает структуру наследования (в виде дерева), легко использовать преимущества объектной модели для создания объектов для композиции.
Для этой ассоциации, которую мы назвали объектом, для разработки всегда следует иметь в виду, что внутреннее состояние объекта и поведение классов разделены. И вы можете благословить / разрешить любой ссылке на данные использовать любое поведение пакета / класса. Так как упаковка позволяет понять "эмоциональное" состояние объекта.
bless($obj,'PackageName')
это встроенная функция Perl, дающая$obj
доступ ко всем'PackageName'
сабы
Например:
use JSON;
#-- create json data
$data = {foo=>"bar"};
#-- lets print stringify using JSON
print JSON::encode_json($data);
# will print: {"foo":"bar"}
#-- let create object
$obj = { "xxx" => "yyy" };
#-- let try bless obj with JSON
bless($obj,'JSON');
#-- $obj still has "xxx"
print $obj->{xxx};
#will print yyy
#-- see above JSON has sub encode_json, now in $obj has all JSON subs
#-- include encode_json lest try:
print $obj->encode_json($data);
#-- error: Usage: JSON::XS::encode_json(scalar)
# why ?
# because $obj->encode_json is same as:
# JSON::encode_json($obj,$data)
# using $obj->mysub is set $obj as FIRST ARGUMENT of PackageName::mysub
# and this encode_json try to stringify $obj that already
# have all subs of package JSON
Например, если вы можете быть уверены, что любой объект 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 извлекает ссылку, которая была передана в качестве первого аргумента, а затем операторы print получают доступ к различным записям благословенного хэша.