Perl: Как создавать объекты "на лету"?
Моя цель - использовать $obj
следующим образом:
print $obj->hello() . $obj->{foo};
И я хотел бы создать объект inline, возможно, используя что-то вроде этого:
my $obj = (
foo => 1,
hello => sub { return 'world' }
);
но когда я пытаюсь использовать $obj
как объект, я получаю сообщение об ошибке, что $obj не был благословлен. Есть ли какой-нибудь базовый класс (например, stdClass
в PHP), я могу использовать, чтобы благословить хэш, чтобы я мог использовать его как объект?
Для тех, кто знает JavaScript, я пытаюсь сделать следующее, но в Perl:
# JS CODE BELOW
var obj = { foo: 1, hello: function () { return 'world' } };
echo obj.hello() + obj.foo;
Ответы
Ответ 1
Perl потребует небольшой помощи для этого. Потому что он не рассматривает ссылки на коды, хранящиеся в хэшах, как "методы". Методы реализуются как записи в таблицу символов пакета. Perl более ориентирован на классы, чем JavaScript, который с гордостью заявляет, что он более объектно-ориентирован (на отдельных объектах).
Чтобы выполнить эту функцию, вам нужно будет создать класс, который сопоставил бы ссылки таким образом. Способ обойти методы в таблице символов - это метод AUTOLOAD
. Если пакет содержит подпрограмму AUTOLOAD
, когда вызов выполняется для благословенного объекта, который Perl не может найти в цепочке наследования, он вызывает AUTOLOAD
и устанавливает переменную our
) пакета (t23) содержат полное имя функции.
Мы получаем имя вызываемого метода, получив последний node (после последнего '::') полностью квалифицированного поднабора. Мы смотрим, есть ли в этом месте coderef, и если есть, мы можем вернуть его.
package AutoObject;
use strict;
use warnings;
use Carp;
use Params::Util qw<_CODE>;
our $AUTOLOAD;
sub AUTOLOAD {
my $method_name = substr( $AUTOLOAD, index( $AUTOLOAD, '::' ) + 2 );
my ( $self ) = @_;
my $meth = _CODE( $self->{$method_name} );
unless ( $meth ) {
Carp::croak( "object does not support method='$method_name'!" );
}
goto &$meth;
}
1;
Затем вы будете благословлять объект в этом классе:
package main;
my $obj
= bless { foo => 1
, hello => sub { return 'world' }
}, 'AutoObject';
print $obj->hello();
Обычно, в AUTOLOAD
sub я "цемент" поведение. То есть, я создаю записи в таблицу символов пакета, чтобы избежать AUTOLOAD
в следующий раз. Но это обычно для разумно определенного поведения класса.
Я также разработал QuickClass
, который создает пакет для каждого объявленного объекта, но содержит много противоречивых таблиц символов, которые теперь, вероятно, лучше сделать с помощью Class::MOP
.
Учитывая предложение Эрика Стром, вы можете добавить следующий код в пакет AutoObject. Sub import
будет вызываться в любое время use
-d AutoObject
(с параметром 'object'
).
# Definition:
sub object ($) { return bless $_[0], __PACKAGE__; };
sub import { # gets called when Perl reads 'use AutoObject;'
shift; # my name
return unless $_[0] eq 'object'; # object is it only export
use Symbol;
*{ Symbol::qualify_to_reference( 'object', scalar caller()) }
= \&object
;
}
И затем, когда вы хотите создать "литерал объекта", вы можете просто сделать:
use AutoObject qw<object>;
И выражение будет выглядеть следующим образом:
object { foo => 1, hello => sub { return 'world' } };
Вы даже можете сделать:
object { name => 'World'
, hello => sub { return "Hello, $_[0]->{name}"; }
}->hello()
;
И у вас есть выражение объектного литерала. Возможно, модуль будет лучше назван Object::Literal
.
Ответ 2
Попробуйте Hash::AsObject
из CPAN.
Ответ 3
Более Perlish подход заключается в создании отдельного пространства имен для вашего объекта желаемых методов и bless
объекта, чтобы сделать эти методы доступными для вашего объекта. Код для этого может быть довольно сукцином.
my $obj = bless { foo => 1 }, "bar";
sub bar::hello { return 'world' };
Как показывает gbacon, если вы хотите написать $obj->{hello}->()
вместо $obj->hello()
, вы можете пропустить операцию благословения.
my $obj = { foo => 1, hello => sub { return 'world' } };
Ответ 4
$obj
будет скаляром, поэтому все, что вы ему назначаете, должно быть скаляром. Вы можете сказать
my %obj = ( foo => 1, hello => sub { return 'world' });
или
my $obj = { foo => 1, hello => sub { return 'world' }};
Последний, с фигурными фигурными скобками, создает хеш-ссылку (которая является скаляром, поэтому она может перейти в $obj
). Однако, чтобы добраться до материала внутри хеш-ссылки, вам нужно использовать оператор стрелки. Что-то вроде $obj->{foo}
или &{$obj->{hello}}
.
Если вам не нужны списки хэшей или что-то в этом роде, обычно лучше использовать первый метод.
В любом случае вы не сможете сказать $obj->hello()
. Perl использует этот синтаксис для своего собственного аромата ООП, который будет иметь функцию hello
в отдельном пакете, на который ссылалась ваша ссылка bless
. Как:
package example;
sub new {} { my $result = {}; return bless $result, 'example' }
sub hello { return 'world' }
package main;
my $obj = example->new();
Как вы можете видеть, методы, которые вы можете вызывать, уже определены, и это не тривиально, чтобы добавить больше. Есть волшебные методы, которые вы можете использовать, чтобы делать такую вещь, но на самом деле это не стоит. &{$obj{hello}}
(или &{$obj->{hello}}
для ссылки) - это меньше усилий, чем попытка заставить Perl работать как Javascript.
Ответ 5
В Perl написано несколько иначе:
my $obj = { foo => 1, hello => sub { return "world" } };
print $obj->{hello}() . $obj->{foo};
Но код неудобен. Предупреждение, которое вы видели о том, что ссылка не была благословлена, говорит вам, что ваши объекты не реализованы в способом, которым Perl ожидает. Оператор bless
помещает объект с пакетом, в котором нужно начать поиск его методов.
Расскажите, что вы хотите сделать с точки зрения проблемной области, и мы можем предложить предложения для более естественного подхода в Perl.
Ответ 6
В любой функции, в которой вы создаете объект, вам нужно вызвать bless
на свой объект, чтобы включить вызов метода.
Например:
package MyClass;
sub new
{
my $obj = {
foo => 1
};
return bless($obj, "MyClass");
}
sub hello
{
my $self = shift;
# Do stuff, including shifting off other arguments if needed
}
package main;
my $obj = MyClass::new();
print "Foo: " . $obj->{foo} . "\n";
$obj->hello();
EDIT: Если вы хотите иметь возможность использовать ссылки подпрограмм для предоставления динамических функциональных возможностей для ваших объектов...
Во-первых, вы можете создать свою ссылку на код так (в этом примере конструктора хэшей):
my $obj = {
foo => 1,
hello => sub { print "Hello\n"; },
}
Затем вы можете вызвать его следующим образом:
my $obj = MyClass::new(); # or whatever
$obj->{hello}->(@myArguments);
Немного громоздко, но он работает. (Возможно, вам даже не понадобится вторая стрелка, но я не уверен.)
Ответ 7
Методы в Perl не являются свойствами объекта, как в Python. Методы - это обычные функции регулярных функций в пакете, связанном с объектом. Регулярные функции, содержащие дополнительный аргумент для самоопределения.
Вы не можете создавать динамически созданные функции как методы.
Вот цитата из perldoc perlobj:
1. An object is simply a reference that happens to know which class it
belongs to.
2. A class is simply a package that happens to provide methods to deal
with object references.
3. A method is simply a subroutine that expects an object reference
(or a package name, for class methods) as the first argument.
О, и bless() - это то, как вы устанавливаете соединение между ссылкой и пакетом.
Ответ 8
Я рекомендую использовать Class:: Struct, как описано в man-странице perltoot.
Вместо того, чтобы перефразировать документацию, позвольте мне процитировать ее, поскольку она хорошо объясняет, как это работает:
"Что он делает, это предоставить вам способ" объявить "класс как объекты, чьи поля имеют определенный тип. Функция, которая делает это, вызывается, что неудивительно, struct(). Поскольку структуры или записи а не базовые типы в Perl, каждый раз, когда вы хотите создать класс для создания объекта данных, подобного записи, вы сами должны определить метод new(), а также отдельные методы доступа к данным для каждого из этих полей записи. быстро соскучиться с этим процессом. Функция Class:: Struct:: struct() облегчает эту скуку."
Все еще цитирование из документа - пример способа его реализации:
use Class::Struct qw(struct);
use Jobbie; # user-defined; see below
struct 'Fred' => {
one => '$',
many => '@',
profession => 'Jobbie', # does not call Jobbie->new()
};
$ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
$ob->many(1, "you");
$ob->many(2, "go");
print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);