Подтвердить что ты не робот

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;
4b9b3361

Ответ 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.

Ответ 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);