Насколько я понимаю, в Perl используется ключевое слово "bless" внутри метода "new" класса:
sub new {
my $self = bless { };
return $self;
}
Но что именно «bless» делает с этой хеш-ссылкой?
Насколько я понимаю, в Perl используется ключевое слово "bless" внутри метода "new" класса:
sub new {
my $self = bless { };
return $self;
}
Но что именно «bless» делает с этой хеш-ссылкой?
Обычно 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-ing заключается в том, что он позволяет применять специальный синтаксис к благословенной ссылке.
Например, если благословенная ссылка хранится в $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;
}
Я дам ответ здесь, так как те, что здесь, мне не совсем понравились.
Функция bless в 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 году нет смысла писать свойства в определенном порядке на динамических языках) и возвращает хеш-таблицу с этими свойствами, или, если вы забыли ввести ключевое слово new, она вернет весь глобальный контекст (например, окно в браузере или глобальный в 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, которая печатает собственный звук животного.
Один из способов добиться этого - научить каждое отдельное Животное издавать звук. Это означает, что у каждой кошки есть собственная дублирующая функция для выполнения звука.
# 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 вроде бы заканчивается.
Оператор new в 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.
})();
Мы хотим иметь одну функцию для каждого Animal, которая будет искать собственный звук этого животного, а не жестко кодировать его при создании.
Благословение позволяет нам использовать пакет в качестве прототипа объектов. Таким образом, объект знает о «пакете», на который он «ссылается», и, в свою очередь, может заставить функции в пакете «обращаться» к конкретным экземплярам, которые были созданы из конструктора этого «объекта пакета»:
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 для этого есть прекрасная магия 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}++}; создаст метод с именем somesub в классе, указанном в $anything. Все это возможно во время выполнения. Однако возможность использования в повседневном коде не является хорошей практикой. Но это полезно при создании систем наложения объектов, таких как Moose или Moo.
- person DavidO; 23.10.2019
unfortunately it makes it impossible(to my understanding) to create "new classes" at runtime претензию. Я предполагаю, что моя проблема в конечном итоге сводилась к тому, что это было значительно менее интуитивно понятным для управления / анализа системы пакетов во время выполнения, но до сих пор мне не удалось показать то, что она по своей сути не может сделать. Система пакетов, похоже, поддерживает все инструменты, необходимые для добавления / удаления / проверки / изменения себя во время выполнения.
- person Dmitry; 09.11.2019
Наряду с рядом хороших ответов, что конкретно отличает ссылку 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
При этом известно, что 1) это объект 2) какому пакету он принадлежит, и это информирует о его использовании.
Например, когда встречается разыменование этой переменной ($obj->name), в пакете (или иерархии) ищется подпрограмма с таким именем, объект передается в качестве первого аргумента и т. Д.
Следуя этой мысли, я руководствуюсь разработкой объектно-ориентированного Perl.
Благослови связывать любую ссылку на структуру данных с классом. Учитывая, как Perl создает структуру наследования (в виде дерева), легко использовать преимущества объектной модели для создания объектов для композиции.
Эту ассоциацию мы назвали объектом, чтобы при разработке всегда иметь в виду, что внутреннее состояние объекта и поведение класса разделены. И вы можете благословить / разрешить любой ссылке на данные использовать любое поведение пакета / класса. С пакетом можно понять «эмоциональное» состояние объекта.
Например, если вы можете быть уверены, что любой объект 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 извлекает ссылку, которая была передана в качестве первого аргумента, а затем операторы печати обращаются к различным записям благословенного хеша.